Camera MVP tests

1. Add media store helper.
2. Add media validation helper.

Test: local test run.
Bug: 162007869
Change-Id: I074e5ebf4ec1942a83d2725054d05f4ee191f8ee
diff --git a/libraries/media-helper/Android.bp b/libraries/media-helper/Android.bp
new file mode 100644
index 0000000..f9340d8
--- /dev/null
+++ b/libraries/media-helper/Android.bp
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2020 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.
+//
+
+java_library {
+    name: "media-helper-lib",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "truth-prebuilt",
+        "guava",
+    ],
+}
diff --git a/libraries/media-helper/src/android/test/mediahelper/MediaStoreHelper.java b/libraries/media-helper/src/android/test/mediahelper/MediaStoreHelper.java
new file mode 100644
index 0000000..12e67ff
--- /dev/null
+++ b/libraries/media-helper/src/android/test/mediahelper/MediaStoreHelper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 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.test.mediahelper;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Helper functions for interacting with android {@link MediaStore}. */
+public class MediaStoreHelper {
+    private static MediaStoreHelper sInstance;
+    private ContentResolver mContentResolver;
+
+    private MediaStoreHelper(Context context) {
+        mContentResolver = context.getContentResolver();
+    }
+
+    public static MediaStoreHelper getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new MediaStoreHelper(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Returns the maximum value of {@link MediaStore.MediaColumns.GENERATION_ADDED} at given URI.
+     *
+     * @param uri The URI, the content:// style uri, for the content to retrieve from {@link
+     *     MediaStore}. For example, to retrieve from image media table, use
+     *     "content://media/external/images/media".
+     * @param selection A filter declaring which rows to return, formatted as an SQL WHERE clause
+     *     (excluding the WHERE itself). Passing null will return all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be replaced by the values
+     *     from selectionArgs, in the order that they appear in the selection. The values will be
+     *     bound as Strings.
+     * @return A long value of the maximum generation. Returns -1L if no records find.
+     */
+    public long getMaxGeneration(Uri uri, String selection, String[] selectionArgs) {
+        long maxGen = -1L;
+        Cursor cursor =
+                mContentResolver.query(
+                        uri,
+                        new String[] {MediaStore.MediaColumns.GENERATION_ADDED},
+                        selection,
+                        selectionArgs,
+                        MediaStore.MediaColumns.GENERATION_ADDED + " DESC");
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                maxGen =
+                        cursor.getLong(
+                                cursor.getColumnIndex(MediaStore.MediaColumns.GENERATION_ADDED));
+            }
+            cursor.close();
+        }
+        return maxGen;
+    }
+
+    /**
+     * Returns a value list of {@link MediaStore.MediaColumns#_ID}.
+     *
+     * @param uri The URI, the content:// style uri, for the content to retrieve from {@link
+     *     MediaStore}. For example, to retrieve from image media table, use
+     *     "content://media/external/images/media".
+     * @param selection A filter declaring which rows to return, formatted as an SQL WHERE clause
+     *     (excluding the WHERE itself). Passing null will return all rows for the given URI.
+     * @param selectionArgs You may include ?s in selection, which will be replaced by the values
+     *     from selectionArgs, in the order that they appear in the selection. The values will be
+     *     bound as Strings.
+     * @param fromGen Generation added. The id of the records returned all have generation bigger
+     *     than this value. It is also formatted as a WHERE clause together with what specified in
+     *     selection.
+     * @return A list of record ids.
+     */
+    public List<Long> getListOfIdsFromMediaStore(
+            Uri uri, String selection, String[] selectionArgs, long fromGen) {
+        ImmutableList.Builder builder = new ImmutableList.Builder();
+        String selectionStr =
+                MediaStore.MediaColumns.GENERATION_ADDED + ">" + String.valueOf(fromGen);
+        if (selection != null && !selection.isEmpty()) {
+            selectionStr = selectionStr + " AND " + selection;
+        }
+        Cursor cursor =
+                mContentResolver.query(
+                        uri,
+                        new String[] {
+                            MediaStore.MediaColumns._ID, MediaStore.MediaColumns.GENERATION_ADDED
+                        },
+                        selectionStr,
+                        selectionArgs,
+                        null);
+        try {
+            while (cursor != null && cursor.moveToNext()) {
+                builder.add(cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID)));
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return builder.build();
+    }
+
+    /**
+     * Returns a list of media {@link FileDescriptor} for specific MIME type.
+     *
+     * @param uri The URI, the content:// style uri, for the content to retrieve from {@link
+     *     MediaStore}. For example, to retrieve from image media table, use
+     *     "content://media/external/images/media".
+     * @param mimeType The MIME type of the media item, eg. "image/jpeg". {@see
+     *     MediaStore.MediaColumns#MIME_TYPE}
+     * @param fromGen Generation added. The returned records all have generation bigger than this.
+     * @return A list of media {@link FileDescriptor} for specified type.
+     */
+    public List<FileDescriptor> getMediaFileDescriptors(Uri uri, String mimeType, long fromGen) {
+        ImmutableList.Builder fileListBuilder = new ImmutableList.Builder();
+        StringBuilder selectionBuilder = new StringBuilder();
+        List<String> argList = new ArrayList<>();
+        selectionBuilder.append(MediaStore.MediaColumns.IS_PENDING + "=?");
+        argList.add("0");
+        if (mimeType != null) {
+            selectionBuilder.append(" AND ");
+            selectionBuilder.append(MediaStore.MediaColumns.MIME_TYPE + "=?");
+            argList.add(mimeType);
+        }
+        List<Long> ids =
+                getListOfIdsFromMediaStore(
+                        uri,
+                        selectionBuilder.toString(),
+                        argList.toArray(new String[argList.size()]),
+                        fromGen);
+        for (long id : ids) {
+            Uri fileUri = ContentUris.withAppendedId(uri, id);
+            try {
+                FileDescriptor fileDescriptor =
+                        mContentResolver.openFileDescriptor(fileUri, "r").getFileDescriptor();
+                fileListBuilder.add(fileDescriptor);
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException("Unable to open file descriptor at " + uri, e);
+            }
+        }
+        return fileListBuilder.build();
+    }
+}
diff --git a/libraries/media-helper/src/android/test/mediahelper/MediaValidationHelper.java b/libraries/media-helper/src/android/test/mediahelper/MediaValidationHelper.java
new file mode 100644
index 0000000..c7a66c4
--- /dev/null
+++ b/libraries/media-helper/src/android/test/mediahelper/MediaValidationHelper.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 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.test.mediahelper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/** Helper functions for validating media files. */
+public class MediaValidationHelper {
+    private static final String TAG = MediaValidationHelper.class.getSimpleName();
+    private static final String VIDEO_MIME_TYPE = "video";
+    private static final String AUDIO_MIME_TYPE = "audio";
+
+    /**
+     * Validate video file.
+     *
+     * <p>This function validates that video contains at least 1 video track and 1 audio track. Also
+     * validate that the video track has expected height and width. It also checks the video track
+     * duration is bigger than the specified length.
+     *
+     * @param fileDescriptor The {@link FileDescriptor} of the video file.
+     * @param expHeight Expected video track height.
+     * @param expWidth Expected video track width.
+     * @param minDurationMillis Minimum duration in milli seconds. The video track duration should
+     *     be longer than it.
+     * @throws IOException
+     */
+    public static void validateVideo(
+            FileDescriptor fileDescriptor, int expHeight, int expWidth, long minDurationMillis)
+            throws IOException {
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(fileDescriptor);
+        int numTracks = extractor.getTrackCount();
+        assertThat(numTracks).isGreaterThan(1);
+        Log.d(TAG, String.format("Number of tracks: %d", numTracks));
+        boolean hasVideoTrack = false;
+        boolean hasAudioTrack = false;
+        for (int i = 0; i < numTracks; i++) {
+            MediaFormat format = extractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            Log.d(TAG, String.format("Mime type: %s; format: %s", mime, format.toString()));
+            if (mime.contains(VIDEO_MIME_TYPE)) {
+                hasVideoTrack = true;
+                validateVideoTrackMediaFormat(format, expHeight, expWidth, minDurationMillis);
+                // TODO(b/164484222): Validate video track frame drop.
+            } else if (mime.contains(AUDIO_MIME_TYPE)) {
+                hasAudioTrack = true;
+            }
+        }
+        assertThat(hasVideoTrack).isTrue();
+        assertThat(hasAudioTrack).isTrue();
+    }
+
+    /**
+     * Validates that video data {@link MediaFormat} is equal to the expected width and height. And
+     * the duration is longer than certain value.
+     *
+     * @param format The {@link MediaFormat} of the video track.
+     * @param expHeight Expected video track height.
+     * @param expWidth Expected video track width.
+     * @param minDurationMillis Minimum duration in milli seconds. The video track duration should
+     *     be longer than it.
+     */
+    private static void validateVideoTrackMediaFormat(
+            MediaFormat format, int expHeight, int expWidth, long minDurationMillis) {
+        long durationMillis =
+                TimeUnit.MICROSECONDS.toMillis(format.getLong(MediaFormat.KEY_DURATION));
+        int width = format.getInteger(MediaFormat.KEY_WIDTH);
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+        Log.d(
+                TAG,
+                String.format(
+                        "Duration: %d; Width: %d; Height: %d", durationMillis, width, height));
+        assertThat(durationMillis).isGreaterThan(minDurationMillis);
+        assertThat(width).isEqualTo(expWidth);
+        assertThat(height).isEqualTo(expHeight);
+    }
+
+    /**
+     * Validates that the image file height and weight is greater than certain value.
+     *
+     * @param fileDescriptor The {@link FileDescriptor} of the image.
+     * @param minHeight Minimum height. The image height should be greater than this value.
+     * @param minWidth Minimum width. The image width should be greater than this value.
+     */
+    public static void validateImage(FileDescriptor fileDescriptor, int minHeight, int minWidth) {
+        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+        int height = bitmap.getHeight();
+        int width = bitmap.getWidth();
+        Log.d(TAG, String.format("Height: %d; Width: %d", height, width));
+        assertThat(height).isGreaterThan(minHeight);
+        assertThat(width).isGreaterThan(minWidth);
+    }
+}