Merge "Add basic jank tests for DocumentsUI." into nyc-dev
diff --git a/packages/DocumentsUI/perf-tests/Android.mk b/packages/DocumentsUI/perf-tests/Android.mk
index 11c163b..919fc56 100644
--- a/packages/DocumentsUI/perf-tests/Android.mk
+++ b/packages/DocumentsUI/perf-tests/Android.mk
@@ -11,7 +11,7 @@
     ../tests/src/com/android/documentsui/StubProvider.java
 
 LOCAL_JAVA_LIBRARIES := android-support-v4 android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator ub-janktesthelper
 
 LOCAL_PACKAGE_NAME := DocumentsUIPerfTests
 LOCAL_INSTRUMENTATION_FOR := DocumentsUI
diff --git a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java
new file mode 100644
index 0000000..cb2d904
--- /dev/null
+++ b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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_2_ID;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import android.content.Intent;
+import android.content.Context;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.uiautomator.UiScrollable;
+import android.util.Log;
+
+import com.android.documentsui.FilesActivity;
+import com.android.documentsui.bots.RootsListBot;
+import com.android.documentsui.bots.DirectoryListBot;
+
+@LargeTest
+public class FilesJankPerfTest extends JankTestBase {
+    private static final String DOCUMENTSUI_PACKAGE = "com.android.documentsui";
+    private static final int MAX_FLINGS = 10;
+    private static final int BOT_TIMEOUT = 5000;
+
+    private RootsListBot mRootsListBot;
+    private DirectoryListBot mDirListBot;
+    private Activity mActivity = null;
+
+    public void setUpInLoop() {
+        final UiDevice device = UiDevice.getInstance(getInstrumentation());
+        final Context context = getInstrumentation().getTargetContext();
+        mRootsListBot = new RootsListBot(device, context, BOT_TIMEOUT);
+        mDirListBot = new DirectoryListBot(device, context, BOT_TIMEOUT);
+
+        final Intent intent = new Intent(context, FilesActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mActivity = getInstrumentation().startActivitySync(intent);
+    }
+
+    public void tearDownInLoop() {
+        if (mActivity != null) {
+            mActivity.finish();
+            mActivity = null;
+        }
+    }
+
+    public void setupAndOpenInLoop() throws Exception {
+        setUpInLoop();
+        openRoot();
+    }
+
+    public void openRoot() throws Exception {
+        mRootsListBot.openRoot(STRESS_ROOT_2_ID);
+    }
+
+    @JankTest(expectedFrames=0, beforeLoop="setUpInLoop", afterLoop="tearDownInLoop")
+    @GfxMonitor(processName=DOCUMENTSUI_PACKAGE)
+    public void testOpenRootJankPerformance() throws Exception {
+        openRoot();
+        getInstrumentation().waitForIdleSync();
+    }
+
+    @JankTest(expectedFrames=0, beforeLoop="setupAndOpenInLoop", afterLoop="tearDownInLoop")
+    @GfxMonitor(processName=DOCUMENTSUI_PACKAGE)
+    public void testFlingJankPerformance() throws Exception {
+        new UiScrollable(mDirListBot.findDocumentsList().getSelector()).flingToEnd(MAX_FLINGS);
+        getInstrumentation().waitForIdleSync();
+    }
+}
diff --git a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
index 1bc802a..6147a71 100644
--- a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
+++ b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
@@ -32,7 +32,9 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 /**
@@ -46,11 +48,21 @@
     // Empty root.
     public static final String STRESS_ROOT_0_ID = "STRESS_ROOT_0";
 
-    // Root with thousands of items.
+    // Root with thousands of directories.
     public static final String STRESS_ROOT_1_ID = "STRESS_ROOT_1";
 
+    // Root with hundreds of files.
+    public static final String STRESS_ROOT_2_ID = "STRESS_ROOT_2";
+
     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 STRESS_ROOT_2_DOC_ID = "STRESS_ROOT_2_DOC";
+
+    private static final int STRESS_ROOT_1_ITEMS = 10000;
+    private static final int STRESS_ROOT_2_ITEMS = 300;
+
+    private static final String MIME_TYPE_IMAGE = "image/jpeg";
+    private static final long REFERENCE_TIMESTAMP = 1459159369359L;
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
@@ -62,7 +74,12 @@
     };
 
     private String mAuthority = DEFAULT_AUTHORITY;
-    private ArrayList<String> mIds = new ArrayList<>();
+
+    // Map from a root document id to children document ids.
+    private Map<String, ArrayList<StubDocument>> mChildDocuments = new HashMap<>();
+
+    private Map<String, StubDocument> mDocuments = new HashMap<>();
+    private Map<String, StubRoot> mRoots = new HashMap<>();
 
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
@@ -72,20 +89,41 @@
 
     @Override
     public boolean onCreate() {
-        mIds = new ArrayList();
-        for (int i = 0; i < 10000; i++) {
-            mIds.add(createRandomId(i));
+        StubDocument document;
+
+        ArrayList<StubDocument> children = new ArrayList<StubDocument>();
+        mChildDocuments.put(STRESS_ROOT_1_DOC_ID, children);
+        for (int i = 0; i < STRESS_ROOT_1_ITEMS; i++) {
+            document = StubDocument.createDirectory(i);
+            mDocuments.put(document.id, document);
+            children.add(document);
         }
-        mIds.add(STRESS_ROOT_0_DOC_ID);
-        mIds.add(STRESS_ROOT_1_DOC_ID);
+
+        children = new ArrayList<StubDocument>();
+        mChildDocuments.put(STRESS_ROOT_2_DOC_ID, children);
+        for (int i = 0; i < STRESS_ROOT_2_ITEMS; i++) {
+            document = StubDocument.createFile(STRESS_ROOT_1_ITEMS + i);
+            mDocuments.put(document.id, document);
+            children.add(document);
+        }
+
+        mRoots.put(STRESS_ROOT_0_ID, new StubRoot(STRESS_ROOT_0_ID, STRESS_ROOT_0_DOC_ID));
+        mRoots.put(STRESS_ROOT_1_ID, new StubRoot(STRESS_ROOT_1_ID, STRESS_ROOT_1_DOC_ID));
+        mRoots.put(STRESS_ROOT_2_ID, new StubRoot(STRESS_ROOT_2_ID, STRESS_ROOT_2_DOC_ID));
+
+        mDocuments.put(STRESS_ROOT_0_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_0_DOC_ID));
+        mDocuments.put(STRESS_ROOT_1_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_1_DOC_ID));
+        mDocuments.put(STRESS_ROOT_2_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_2_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);
+        for (StubRoot root : mRoots.values()) {
+            includeRoot(result, root);
+        }
         return result;
     }
 
@@ -93,57 +131,112 @@
     public Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
-        includeDocument(result, documentId);
+        final StubDocument document = mDocuments.get(documentId);
+        includeDocument(result, document);
         return result;
     }
 
     @Override
-    public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
+    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);
+        final ArrayList<StubDocument> childDocuments = mChildDocuments.get(parentDocumentId);
+        if (childDocuments != null) {
+            for (StubDocument document : childDocuments) {
+                includeDocument(result, document);
             }
         }
         return result;
     }
 
     @Override
-    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+    public ParcelFileDescriptor openDocument(String docId, String mode,
+            CancellationSignal signal)
             throws FileNotFoundException {
         throw new UnsupportedOperationException();
     }
 
-    private void includeRoot(MatrixCursor result, String rootId, String docId) {
+    private void includeRoot(MatrixCursor result, StubRoot root) {
         final RowBuilder row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, rootId);
+        row.add(Root.COLUMN_ROOT_ID, root.id);
         row.add(Root.COLUMN_FLAGS, 0);
-        row.add(Root.COLUMN_TITLE, rootId);
-        row.add(Root.COLUMN_DOCUMENT_ID, docId);
+        row.add(Root.COLUMN_TITLE, root.id);
+        row.add(Root.COLUMN_DOCUMENT_ID, root.documentId);
     }
 
-    private void includeDocument(MatrixCursor result, String id) {
+    private void includeDocument(MatrixCursor result, StubDocument document) {
         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_DOCUMENT_ID, document.id);
+        row.add(Document.COLUMN_DISPLAY_NAME, document.id);
+        row.add(Document.COLUMN_SIZE, document.size);
+        row.add(Document.COLUMN_MIME_TYPE, document.mimeType);
         row.add(Document.COLUMN_FLAGS, 0);
-        row.add(Document.COLUMN_LAST_MODIFIED, null);
+        row.add(Document.COLUMN_LAST_MODIFIED, document.lastModified);
     }
 
-    private static String getDocumentIdForFile(File file) {
+    private static String getStubDocumentIdForFile(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));
+    private static class StubDocument {
+        final String mimeType;
+        final String id;
+        final int size;
+        final long lastModified;
+
+        private StubDocument(String mimeType, String id, int size, long lastModified) {
+            this.mimeType = mimeType;
+            this.id = id;
+            this.size = size;
+            this.lastModified = lastModified;
         }
-        builder.append(index);  // Append a number to guarantee uniqueness.
-        return builder.toString();
+
+        public static StubDocument createDirectory(int index) {
+            return new StubDocument(
+                    DocumentsContract.Document.MIME_TYPE_DIR, createRandomId(index), 0,
+                    createRandomTime(index));
+        }
+
+        public static StubDocument createDirectory(String id) {
+            return new StubDocument(DocumentsContract.Document.MIME_TYPE_DIR, id, 0, 0);
+        }
+
+        public static StubDocument createFile(int index) {
+            return new StubDocument(
+                    MIME_TYPE_IMAGE, createRandomId(index), createRandomSize(index),
+                    createRandomTime(index));
+        }
+
+        private static 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();
+        }
+
+        private static int createRandomSize(int index) {
+            final Random random = new Random(index);
+            return random.nextInt(1024 * 1024 * 100);  // Up to 100 MB.
+        }
+
+        private static long createRandomTime(int index) {
+            final Random random = new Random(index);
+            // Up to 30 days backwards from REFERENCE_TIMESTAMP.
+            return REFERENCE_TIMESTAMP - random.nextLong() % 1000L * 60 * 60 * 24 * 30;
+        }
+    }
+
+    private static class StubRoot {
+        final String id;
+        final String documentId;
+
+        public StubRoot(String id, String documentId) {
+            this.id = id;
+            this.documentId = documentId;
+        }
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
index da9f9c3..7c1e2196 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
@@ -167,7 +167,7 @@
         return true;
     }
 
-    private UiObject findDocumentsList() {
+    public UiObject findDocumentsList() {
         return findObject(
                 "com.android.documentsui:id/container_directory",
                 DIR_LIST_ID);