Merge "Add allowlist for Mediaprovider" into tm-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 39059fa..2fc29df 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -61,9 +61,6 @@
             "name": "fuse_node_test"
         },
         {
-            "name": "CtsMediaProviderTranscodeTests"
-        },
-        {
             "name": "CtsPhotoPickerTest"
         }
     ],
@@ -72,6 +69,10 @@
             "name": "MediaProviderClientTests"
         },
         {
+            // TODO(b/222253890): Move these tests back to presubmit once the bug is fixed.
+            "name": "CtsMediaProviderTranscodeTests"
+        },
+        {
             "name": "CtsAppSecurityHostTestCases",
             "options": [
                 {
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index e63dd36..07b77b0 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -9,7 +9,7 @@
     method public final int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]);
     method @NonNull public final String getType(@NonNull android.net.Uri);
     method @NonNull public final android.net.Uri insert(@NonNull android.net.Uri, @NonNull android.content.ContentValues);
-    method @Nullable public android.provider.CloudMediaProvider.SurfaceController onCreateSurfaceController(@NonNull android.os.Bundle, @NonNull android.provider.CloudMediaProvider.SurfaceEventCallback);
+    method @Nullable public android.provider.CloudMediaProvider.CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull android.os.Bundle, @NonNull android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback);
     method @NonNull public abstract android.os.Bundle onGetMediaCollectionInfo(@NonNull android.os.Bundle);
     method @NonNull public abstract android.os.ParcelFileDescriptor onOpenMedia(@NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method @NonNull public abstract android.content.res.AssetFileDescriptor onOpenPreview(@NonNull String, @NonNull android.graphics.Point, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -26,8 +26,8 @@
     method public final int update(@NonNull android.net.Uri, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
-  public abstract static class CloudMediaProvider.SurfaceController {
-    ctor public CloudMediaProvider.SurfaceController();
+  public abstract static class CloudMediaProvider.CloudMediaSurfaceController {
+    ctor public CloudMediaProvider.CloudMediaSurfaceController();
     method public abstract void onConfigChange(@NonNull android.os.Bundle);
     method public abstract void onDestroy();
     method public abstract void onMediaPause(int);
@@ -40,7 +40,7 @@
     method public abstract void onSurfaceDestroyed(int);
   }
 
-  public static final class CloudMediaProvider.SurfaceEventCallback {
+  public static final class CloudMediaProvider.CloudMediaSurfaceEventCallback {
     method public void onPlaybackEvent(int, int, @Nullable android.os.Bundle);
     field public static final int PLAYBACK_EVENT_BUFFERING = 1; // 0x1
     field public static final int PLAYBACK_EVENT_COMPLETED = 5; // 0x5
@@ -55,6 +55,7 @@
     field public static final String EXTRA_FILTER_ALBUM = "android.provider.extra.FILTER_ALBUM";
     field public static final String EXTRA_FILTER_MIME_TYPE = "android.provider.extra.FILTER_MIME_TYPE";
     field public static final String EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED";
+    field public static final String EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID";
     field public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
     field public static final String EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL";
     field public static final String EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED";
@@ -118,7 +119,7 @@
     method public static boolean isCurrentCloudMediaProviderAuthority(@NonNull android.content.ContentResolver, @NonNull String);
     method public static boolean isCurrentSystemGallery(@NonNull android.content.ContentResolver, int, @NonNull String);
     method public static boolean isSupportedCloudMediaProviderAuthority(@NonNull android.content.ContentResolver, @NonNull String);
-    method public static boolean notifyCloudMediaChangedEvent(@NonNull android.content.ContentResolver, @NonNull String);
+    method public static void notifyCloudMediaChangedEvent(@NonNull android.content.ContentResolver, @NonNull String, @NonNull String) throws java.lang.SecurityException;
     method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
     method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
     field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
diff --git a/apex/framework/java/android/provider/CloudMediaProvider.java b/apex/framework/java/android/provider/CloudMediaProvider.java
index 24a68d6..048aba0 100644
--- a/apex/framework/java/android/provider/CloudMediaProvider.java
+++ b/apex/framework/java/android/provider/CloudMediaProvider.java
@@ -188,6 +188,11 @@
      * {@link CloudMediaProviderContract.MediaColumns#DATE_TAKEN_MILLIS}, i.e. most recent items
      * first.
      * <p>
+     * The cloud media provider must set the
+     * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
+     * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
+     * returned {@link Cursor}.
+     * <p>
      * If the cloud media provider handled any filters in {@code extras}, it must add the key to
      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
      * {@link Cursor#setExtras} {@link Bundle}.
@@ -210,6 +215,11 @@
      * within the current provider version as returned by {@link #onGetMediaCollectionInfo}. These
      * items can be optionally filtered by {@code extras}.
      * <p>
+     * The cloud media provider must set the
+     * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
+     * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
+     * returned {@link Cursor}.
+     * <p>
      * If the provider handled any filters in {@code extras}, it must add the key to
      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
      * {@link Cursor#setExtras} {@link Bundle}.
@@ -232,6 +242,11 @@
      * {@link CloudMediaProviderContract.AlbumColumns#DATE_TAKEN_MILLIS}, i.e. most recent items
      * first.
      * <p>
+     * The cloud media provider must set the
+     * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
+     * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
+     * returned {@link Cursor}.
+     * <p>
      * If the provider handled any filters in {@code extras}, it must add the key to
      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
      * {@link Cursor#setExtras} {@link Bundle}.
@@ -300,23 +315,20 @@
             throws FileNotFoundException;
 
     /**
-     * Returns a {@link SurfaceController} used for rendering the preview of media items, or null
-     * if preview rendering is not supported.
+     * Returns a {@link CloudMediaSurfaceController} used for rendering the preview of media items,
+     * or null if preview rendering is not supported.
      *
-     * <p>This is meant to be called on the main thread, hence the implementation should not block
-     * by performing any heavy operation.
-     *
-     * @param config containing configuration parameters for {@link SurfaceController}
+     * @param config containing configuration parameters for {@link CloudMediaSurfaceController}
      * <ul>
      * <li> {@link CloudMediaProviderContract#EXTRA_LOOPING_PLAYBACK_ENABLED}
      * <li> {@link CloudMediaProviderContract#EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED}
      * </ul>
-     * @param callback {@link SurfaceEventCallback} to send event updates for {@link Surface} to
-     *                 picker launched via {@link MediaStore#ACTION_PICK_IMAGES}
+     * @param callback {@link CloudMediaSurfaceEventCallback} to send event updates for
+     *                 {@link Surface} to picker launched via {@link MediaStore#ACTION_PICK_IMAGES}
      */
     @Nullable
-    public SurfaceController onCreateSurfaceController(@NonNull Bundle config,
-            @NonNull SurfaceEventCallback callback) {
+    public CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull Bundle config,
+            @NonNull CloudMediaSurfaceEventCallback callback) {
         return null;
     }
 
@@ -344,7 +356,7 @@
         if (METHOD_GET_MEDIA_COLLECTION_INFO.equals(method)) {
             return onGetMediaCollectionInfo(extras);
         } else if (METHOD_CREATE_SURFACE_CONTROLLER.equals(method)) {
-            return onCreateSurfaceController(extras);
+            return onCreateCloudMediaSurfaceController(extras);
         } else if (METHOD_GET_ASYNC_CONTENT_PROVIDER.equals(method)) {
             return onGetAsyncContentProvider();
         } else {
@@ -352,7 +364,7 @@
         }
     }
 
-    private Bundle onCreateSurfaceController(@NonNull Bundle extras) {
+    private Bundle onCreateCloudMediaSurfaceController(@NonNull Bundle extras) {
         Objects.requireNonNull(extras);
 
         final IBinder binder = extras.getBinder(EXTRA_SURFACE_EVENT_CALLBACK);
@@ -360,21 +372,23 @@
             throw new IllegalArgumentException("Missing surface event callback");
         }
 
-        final SurfaceEventCallback callback =
-                new SurfaceEventCallback(ICloudSurfaceEventCallback.Stub.asInterface(binder));
+        final CloudMediaSurfaceEventCallback callback =
+                new CloudMediaSurfaceEventCallback(
+                        ICloudSurfaceEventCallback.Stub.asInterface(binder));
         final Bundle config = new Bundle();
         config.putBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED, DEFAULT_LOOPING_PLAYBACK_ENABLED);
         config.putBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED,
                 DEFAULT_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED);
-        final SurfaceController controller = onCreateSurfaceController(config, callback);
+        final CloudMediaSurfaceController controller =
+                onCreateCloudMediaSurfaceController(config, callback);
         if (controller == null) {
-            Log.d(TAG, "onCreateSurfaceController returned null");
+            Log.d(TAG, "onCreateCloudMediaSurfaceController returned null");
             return Bundle.EMPTY;
         }
 
         Bundle result = new Bundle();
         result.putBinder(EXTRA_SURFACE_CONTROLLER,
-                new SurfaceControllerWrapper(controller).asBinder());
+                new CloudMediaSurfaceControllerWrapper(controller).asBinder());
         return result;
     }
 
@@ -545,12 +559,12 @@
      *
      * <p>The methods of this class are meant to be asynchronous, and should not block by performing
      * any heavy operation.
-     * <p>Note that a single SurfaceController instance would be responsible for
+     * <p>Note that a single CloudMediaSurfaceController instance would be responsible for
      * rendering multiple media items associated with multiple surfaces.
      */
     @SuppressLint("PackageLayering") // We need to pass in a Surface which can be prepared for
     // rendering a media item.
-    public static abstract class SurfaceController {
+    public static abstract class CloudMediaSurfaceController {
 
         /**
          * Creates any player resource(s) needed for rendering.
@@ -632,7 +646,7 @@
         public abstract void onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis);
 
         /**
-         * Changes the configuration parameters for the SurfaceController.
+         * Changes the configuration parameters for the CloudMediaSurfaceController.
          *
          * @param config the updated config to change to. This can include config changes for the
          * following:
@@ -644,10 +658,15 @@
         public abstract void onConfigChange(@NonNull Bundle config);
 
         /**
-         * Indicates destruction of this SurfaceController object.
+         * Indicates destruction of this CloudMediaSurfaceController object.
          *
-         * <p>This SurfaceController object should no longer be in use after this method has been
-         * called.
+         * <p>This CloudMediaSurfaceController object should no longer be in use after this method
+         * has been called.
+         *
+         * <p>Note that it is possible for this method to be called directly without
+         * {@link #onPlayerRelease} being called, hence you should release any resources associated
+         * with this CloudMediaSurfaceController object, or perform any cleanup required in this
+         * method.
          */
         public abstract void onDestroy();
     }
@@ -658,7 +677,7 @@
      *
      * @see MediaStore#ACTION_PICK_IMAGES
      */
-    public static final class SurfaceEventCallback {
+    public static final class CloudMediaSurfaceEventCallback {
 
         /** {@hide} */
         @IntDef(flag = true, prefix = { "PLAYBACK_EVENT_" }, value = {
@@ -710,7 +729,7 @@
 
         private final ICloudSurfaceEventCallback mCallback;
 
-        SurfaceEventCallback (ICloudSurfaceEventCallback callback) {
+        CloudMediaSurfaceEventCallback (ICloudSurfaceEventCallback callback) {
             mCallback = callback;
         }
 
@@ -731,7 +750,7 @@
             try {
                 mCallback.onPlaybackEvent(surfaceId, playbackEventType, playbackEventInfo);
             } catch (Exception e) {
-                Log.d(TAG, "Failed to notify playback event (" + playbackEventType + ") for "
+                Log.w(TAG, "Failed to notify playback event (" + playbackEventType + ") for "
                         + "surfaceId: " + surfaceId + " ; playbackEventInfo: " + playbackEventInfo,
                         e);
             }
@@ -739,11 +758,12 @@
     }
 
     /** {@hide} */
-    private static class SurfaceControllerWrapper extends ICloudMediaSurfaceController.Stub {
+    private static class CloudMediaSurfaceControllerWrapper
+            extends ICloudMediaSurfaceController.Stub {
 
-        final private SurfaceController mSurfaceController;
+        final private CloudMediaSurfaceController mSurfaceController;
 
-        SurfaceControllerWrapper(SurfaceController surfaceController) {
+        CloudMediaSurfaceControllerWrapper(CloudMediaSurfaceController surfaceController) {
             mSurfaceController = surfaceController;
         }
 
diff --git a/apex/framework/java/android/provider/CloudMediaProviderContract.java b/apex/framework/java/android/provider/CloudMediaProviderContract.java
index 22abc5d..dcc0d08 100644
--- a/apex/framework/java/android/provider/CloudMediaProviderContract.java
+++ b/apex/framework/java/android/provider/CloudMediaProviderContract.java
@@ -391,6 +391,26 @@
     public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
 
     /**
+     * {@link MediaCollectionInfo#MEDIA_COLLECTION_ID} on which the media or album query occurred.
+     *
+     * <p>
+     * Providers must set this token as part of the {@link Cursor#setExtras}
+     * {@link Bundle} returned from the cursors on query.
+     * This allows the OS to verify that the returned results match the
+     * {@link MediaCollectionInfo#MEDIA_COLLECTION_ID} queried via
+     * {@link CloudMediaProvider#onGetMediaCollectionInfo}. If the collection differs, the OS will
+     * ignore the result and may try again.
+     *
+     * @see CloudMediaProvider#onQueryMedia
+     * @see CloudMediaProvider#onQueryDeletedMedia
+     * @see CloudMediaProvider#onQueryAlbums
+     * <p>
+     * Type: STRING
+     */
+    public static final String EXTRA_MEDIA_COLLECTION_ID =
+            "android.provider.extra.MEDIA_COLLECTION_ID";
+
+    /**
      * Generation number to fetch the latest media or album metadata changes from the media
      * collection.
      * <p>
@@ -491,7 +511,7 @@
     public static final String METHOD_GET_MEDIA_COLLECTION_INFO = "android:getMediaCollectionInfo";
 
     /**
-     * Constant used to execute {@link CloudMediaProvider#onCreateSurfaceController} via
+     * Constant used to execute {@link CloudMediaProvider#onCreateCloudMediaSurfaceController} via
      * {@link ContentProvider#call}.
      *
      * {@hide}
@@ -499,7 +519,7 @@
     public static final String METHOD_CREATE_SURFACE_CONTROLLER = "android:createSurfaceController";
 
     /**
-     * Gets surface controller from {@link CloudMediaProvider#onCreateSurfaceController}.
+     * Gets surface controller from {@link CloudMediaProvider#onCreateCloudMediaSurfaceController}.
      * {@hide}
      */
     public static final String EXTRA_SURFACE_CONTROLLER =
@@ -510,8 +530,8 @@
      * <p>
      * In case this is not present, the default value should be false.
      *
-     * @see CloudMediaProvider#onCreateSurfaceController
-     * @see CloudMediaProvider.SurfaceController#onConfigChange
+     * @see CloudMediaProvider#onCreateCloudMediaSurfaceController
+     * @see CloudMediaProvider.CloudMediaSurfaceController#onConfigChange
      * <p>
      * Type: BOOLEAN
      * By default, the value is true
@@ -522,8 +542,8 @@
     /**
      * Indicates whether to mute audio during preview of media items.
      *
-     * @see CloudMediaProvider#onCreateSurfaceController
-     * @see CloudMediaProvider.SurfaceController#onConfigChange
+     * @see CloudMediaProvider#onCreateCloudMediaSurfaceController
+     * @see CloudMediaProvider.CloudMediaSurfaceController#onConfigChange
      * <p>
      * Type: BOOLEAN
      * By default, the value is false
@@ -602,7 +622,7 @@
     public static final String URI_PATH_MEDIA_COLLECTION_INFO = "media_collection_info";
 
     /**
-     * URI path for {@link CloudMediaProvider#onCreateSurfaceController}
+     * URI path for {@link CloudMediaProvider#onCreateCloudMediaSurfaceController}
      *
      * {@hide}
      */
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 84923f4..8d5db4e 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -4713,9 +4713,12 @@
      *
      * @return {@code true} if the notification was successful, {@code false} otherwise
      */
-    public static boolean notifyCloudMediaChangedEvent(@NonNull ContentResolver resolver,
-            @NonNull String authority) {
-        return callForCloudProvider(resolver, NOTIFY_CLOUD_MEDIA_CHANGED_EVENT_CALL, authority);
+    public static void notifyCloudMediaChangedEvent(@NonNull ContentResolver resolver,
+            @NonNull String authority, @NonNull String currentMediaCollectionId)
+            throws SecurityException {
+        if (!callForCloudProvider(resolver, NOTIFY_CLOUD_MEDIA_CHANGED_EVENT_CALL, authority)) {
+            throw new SecurityException("Failed to notify cloud media changed event");
+        }
     }
 
     private static boolean callForCloudProvider(ContentResolver resolver, String method,
diff --git a/res/color/picker_chip_background_color.xml b/res/color/picker_chip_background_color.xml
deleted file mode 100644
index 9eb418e..0000000
--- a/res/color/picker_chip_background_color.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2021 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
-
-      https://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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="?attr/pickerSelectedChipBackgroundColor"/>
-    <item android:state_enabled="true" android:color="?attr/pickerChipBackgroundColor"/>
-</selector>
diff --git a/res/color/picker_chip_ripple_color.xml b/res/color/picker_chip_ripple_color.xml
deleted file mode 100644
index 6264a73..0000000
--- a/res/color/picker_chip_ripple_color.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2021 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
-
-      https://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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Selected. -->
-    <item android:state_pressed="true" android:state_selected="true"
-          android:alpha="0.16" android:color="?android:colorSecondary"/>
-    <item android:state_focused="true" android:state_hovered="true" android:state_selected="true"
-          android:alpha="0.16" android:color="?android:colorSecondary"/>
-    <item android:state_focused="true" android:state_selected="true"
-          android:alpha="0.12" android:color="?android:colorSecondary"/>
-    <item android:state_hovered="true" android:state_selected="true"
-          android:alpha="0.04" android:color="?android:colorSecondary"/>
-    <item android:state_selected="true"
-          android:alpha="0.00" android:color="?android:colorSecondary"/>
-
-    <!-- Unselected. -->
-    <item android:state_pressed="true"
-          android:alpha="0.16" android:color="?android:textColorSecondary"/>
-    <item android:state_focused="true" android:state_hovered="true"
-          android:alpha="0.16" android:color="?android:textColorSecondary"/>
-    <item android:state_focused="true"
-          android:alpha="0.12" android:color="?android:textColorSecondary"/>
-    <item android:state_hovered="true"
-          android:alpha="0.04" android:color="?android:textColorSecondary"/>
-    <item android:alpha="0.00" android:color="?android:textColorSecondary"/>
-</selector>
diff --git a/res/color/picker_chip_text_color.xml b/res/color/picker_chip_text_color.xml
deleted file mode 100644
index d9f15a8..0000000
--- a/res/color/picker_chip_text_color.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2021 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
-
-      https://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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="?attr/pickerSelectedChipTextColor"/>
-    <item android:state_enabled="true" android:color="?android:attr/textColorSecondary"/>
-</selector>
diff --git a/res/drawable/picker_tab_background.xml b/res/drawable/picker_tab_background.xml
new file mode 100644
index 0000000..2c0af95
--- /dev/null
+++ b/res/drawable/picker_tab_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:gravity="center">
+        <shape android:shape="rectangle">
+            <size
+                android:width="@dimen/picker_tab_width"
+                android:height="@dimen/picker_tab_height" />
+            <corners android:radius="@dimen/picker_tab_radius"/>
+            <solid android:color="?attr/pickerTabBackgroundColor"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/res/drawable/picker_tab_indicator.xml b/res/drawable/picker_tab_indicator.xml
new file mode 100644
index 0000000..626472f
--- /dev/null
+++ b/res/drawable/picker_tab_indicator.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:gravity="center">
+        <shape android:shape="rectangle">
+            <size
+                android:width="@dimen/picker_tab_width"
+                android:height="@dimen/picker_tab_height" />
+            <corners android:radius="@dimen/picker_tab_radius"/>
+            <solid android:color="?attr/pickerSelectedTabBackgroundColor"/>
+        </shape>
+    </item>
+</layer-list>
diff --git a/res/layout/activity_photo_picker.xml b/res/layout/activity_photo_picker.xml
index e52915f..5e5460d 100644
--- a/res/layout/activity_photo_picker.xml
+++ b/res/layout/activity_photo_picker.xml
@@ -68,13 +68,16 @@
             <androidx.fragment.app.FragmentContainerView
                 android:id="@+id/fragment_container"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"/>
+                android:layout_height="match_parent"
+                android:accessibilityTraversalAfter="@+id/profile_button"/>
 
             <com.google.android.material.appbar.AppBarLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_gravity="top"
                 android:background="@android:color/transparent"
+                android:importantForAccessibility="yes"
+                android:accessibilityTraversalAfter="@+id/privacy_text"
                 app:liftOnScroll="true">
 
                 <androidx.appcompat.widget.Toolbar
@@ -85,17 +88,84 @@
                     app:titleTextColor="?attr/pickerTextColor"
                     app:titleTextAppearance="@style/PickerToolbarTitleTextAppearance">
 
-                    <LinearLayout
-                        android:id="@+id/chip_container"
+                    <com.google.android.material.tabs.TabLayout
+                        android:id="@+id/tab_layout"
                         android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
+                        android:layout_height="wrap_content"
+                        android:background="@color/picker_background_color"
                         android:layout_gravity="center"
-                        android:orientation="horizontal"/>
+                        app:tabBackground="@drawable/picker_tab_background"
+                        app:tabIndicatorAnimationMode="linear"
+                        app:tabIndicatorColor="?attr/pickerSelectedTabBackgroundColor"
+                        app:tabIndicatorGravity="center"
+                        app:tabMinWidth="@dimen/picker_tab_min_width"
+                        app:tabPaddingStart="@dimen/picker_tab_horizontal_gap"
+                        app:tabPaddingEnd="@dimen/picker_tab_horizontal_gap"
+                        app:tabRippleColor="@null"
+                        app:tabSelectedTextColor="?attr/pickerSelectedTabTextColor"
+                        app:tabTextAppearance="@style/PickerTabTextAppearance"
+                        app:tabTextColor="?android:attr/textColorSecondary" />
 
                 </androidx.appcompat.widget.Toolbar>
 
             </com.google.android.material.appbar.AppBarLayout>
 
+            <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+                android:id="@+id/profile_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/picker_profile_button_margin_bottom"
+                android:layout_gravity="bottom|center"
+                android:textAppearance="@style/PickerButtonTextAppearance"
+                android:textColor="?attr/pickerProfileButtonTextColor"
+                android:text="@string/picker_work_profile"
+                android:visibility="gone"
+                android:accessibilityTraversalAfter="@+id/tab_layout"
+                app:backgroundTint="?attr/pickerProfileButtonColor"
+                app:borderWidth="0dp"
+                app:elevation="3dp"
+                app:icon="@drawable/ic_work_outline"/>
+
+            <FrameLayout
+                android:id="@+id/picker_bottom_bar"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/picker_bottom_bar_size"
+                android:layout_gravity="bottom"
+                android:background="@color/picker_background_color"
+                android:elevation="@dimen/picker_bottom_bar_elevation"
+                android:visibility="gone">
+
+                <Button
+                    android:id="@+id/button_view_selected"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/picker_bottom_bar_horizontal_gap"
+                    android:layout_gravity="start|center_vertical"
+                    android:paddingVertical="@dimen/picker_bottom_bar_buttons_vertical_gap"
+                    android:text="@string/picker_view_selected"
+                    android:textAllCaps="false"
+                    android:textColor="?attr/pickerSelectedColor"
+                    app:icon="@drawable/ic_collections"
+                    app:iconPadding="@dimen/picker_viewselected_icon_padding"
+                    app:iconSize="@dimen/picker_viewselected_icon_size"
+                    app:iconTint="?attr/pickerSelectedColor"
+                    style="@style/MaterialBorderlessButtonStyle"/>
+
+                <Button
+                    android:id="@+id/button_add"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/picker_bottom_bar_horizontal_gap"
+                    android:layout_gravity="end|center_vertical"
+                    android:paddingVertical="@dimen/picker_bottom_bar_buttons_vertical_gap"
+                    android:text="@string/add"
+                    android:textAllCaps="false"
+                    android:textColor="?attr/pickerHighlightTextColor"
+                    android:backgroundTint="?attr/pickerHighlightColor"
+                    style="@style/MaterialButtonStyle"/>
+
+            </FrameLayout>
+
         </FrameLayout>
 
     </LinearLayout>
diff --git a/res/layout/fragment_picker_tab.xml b/res/layout/fragment_picker_tab.xml
index e1590a6..ae3180d 100644
--- a/res/layout/fragment_picker_tab.xml
+++ b/res/layout/fragment_picker_tab.xml
@@ -57,57 +57,4 @@
         android:drawSelectorOnTop="true"
         android:overScrollMode="never"/>
 
-    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
-        android:id="@+id/profile_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/picker_profile_button_margin_bottom"
-        android:layout_gravity="bottom|center"
-        android:textAppearance="@style/PickerButtonTextAppearance"
-        android:textColor="?attr/pickerProfileButtonTextColor"
-        android:text="@string/picker_work_profile"
-        android:visibility="gone"
-        app:backgroundTint="?attr/pickerProfileButtonColor"
-        app:borderWidth="0dp"
-        app:elevation="3dp"
-        app:icon="@drawable/ic_work_outline"
-    />
-
-    <FrameLayout
-        android:id="@+id/picker_bottom_bar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/picker_bottom_bar_size"
-        android:layout_gravity="bottom"
-        android:background="@color/picker_background_color"
-        android:elevation="@dimen/picker_bottom_bar_elevation"
-        android:visibility="gone">
-
-        <Button
-            android:id="@+id/button_view_selected"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginHorizontal="@dimen/picker_bottom_bar_horizontal_gap"
-            android:layout_gravity="start|center_vertical"
-            android:paddingVertical="@dimen/picker_bottom_bar_buttons_vertical_gap"
-            android:drawableLeft="@drawable/ic_collections"
-            android:text="@string/picker_view_selected"
-            android:textAllCaps="false"
-            android:textColor="?attr/pickerSelectedColor"
-            app:iconPadding="@dimen/picker_viewselected_icon_padding"
-            style="@style/MaterialBorderlessButtonStyle"/>
-
-        <Button
-            android:id="@+id/button_add"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginHorizontal="@dimen/picker_bottom_bar_horizontal_gap"
-            android:layout_gravity="end|center_vertical"
-            android:paddingVertical="@dimen/picker_bottom_bar_buttons_vertical_gap"
-            android:text="@string/add"
-            android:textAllCaps="false"
-            android:textColor="?attr/pickerHighlightTextColor"
-            android:backgroundTint="?attr/pickerHighlightColor"
-            style="@style/MaterialButtonStyle"/>
-
-    </FrameLayout>
 </FrameLayout>
diff --git a/res/layout/fragment_picker_tab_container.xml b/res/layout/fragment_picker_tab_container.xml
new file mode 100644
index 0000000..41971b8
--- /dev/null
+++ b/res/layout/fragment_picker_tab_container.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<androidx.viewpager2.widget.ViewPager2
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/picker_tab_viewpager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/res/layout/fragment_preview.xml b/res/layout/fragment_preview.xml
index 550c7ca..7db607f 100644
--- a/res/layout/fragment_preview.xml
+++ b/res/layout/fragment_preview.xml
@@ -65,7 +65,7 @@
 
         <!-- Buttons for Preview on View Selected. Hidden by default -->
         <Button
-            android:id="@+id/preview_select_check_button"
+            android:id="@+id/preview_selected_check_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="start|center_vertical"
@@ -75,7 +75,7 @@
             android:drawableLeft="@drawable/preview_check"
             android:drawableTint="@color/preview_highlight_color"
             android:textAllCaps="false"
-            android:text="@string/deselect"
+            android:text="@string/selected"
             android:textColor="@color/picker_default_white"
             android:visibility="gone"
             style="@style/MaterialBorderlessButtonStyle"/>
diff --git a/res/layout/item_cloud_video_preview.xml b/res/layout/item_cloud_video_preview.xml
index e98b9bb..828f618 100644
--- a/res/layout/item_cloud_video_preview.xml
+++ b/res/layout/item_cloud_video_preview.xml
@@ -24,4 +24,12 @@
         android:id="@+id/preview_player_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
+
+    <ImageView
+        android:id="@+id/preview_video_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:scaleType="fitCenter"
+        android:contentDescription="@null" />
 </FrameLayout>
diff --git a/res/layout/picker_chip_tab_header.xml b/res/layout/picker_chip_tab_header.xml
deleted file mode 100644
index 700f5e6..0000000
--- a/res/layout/picker_chip_tab_header.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-
-<com.google.android.material.chip.Chip
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:layout_marginHorizontal="@dimen/picker_chip_horizontal_gap"
-    android:textAppearance="@style/PickerChipTextAppearance"
-    android:textColor="@color/picker_chip_text_color"
-    app:chipBackgroundColor="@color/picker_chip_background_color"
-    app:chipCornerRadius="@dimen/picker_chip_radius"
-    app:chipStrokeWidth="0dp"
-    app:rippleColor="@color/picker_chip_ripple_color"
-    app:chipMinTouchTargetSize="@dimen/picker_chip_touch_size"/>
diff --git a/res/values-night-v31/styles.xml b/res/values-night-v31/styles.xml
index 4a34a11..27f8794 100644
--- a/res/values-night-v31/styles.xml
+++ b/res/values-night-v31/styles.xml
@@ -18,13 +18,15 @@
 
     <style name="PickerMaterialTheme" parent="@style/Theme.Material3.DayNight.NoActionBar">
         <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
-        <item name="pickerChipBackgroundColor">@android:color/system_neutral1_700</item>
+        <item name="pickerTabBackgroundColor">@android:color/system_neutral1_700</item>
         <item name="pickerHighlightColor">@android:color/system_accent2_100</item>
         <item name="pickerHighlightTextColor">?android:attr/textColorPrimaryInverse</item>
         <item name="pickerProfileButtonColor">@android:color/system_accent2_100</item>
+        <item name="pickerDisabledProfileButtonColor">@android:color/system_neutral1_700</item>
         <item name="pickerProfileButtonTextColor">?android:attr/textColorPrimaryInverse</item>
-        <item name="pickerSelectedChipBackgroundColor">@android:color/system_accent2_100</item>
-        <item name="pickerSelectedChipTextColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="pickerDisabledProfileButtonTextColor">?android:attr/textColorTertiary</item>
+        <item name="pickerSelectedTabBackgroundColor">@android:color/system_accent2_100</item>
+        <item name="pickerSelectedTabTextColor">?android:attr/textColorPrimaryInverse</item>
         <item name="pickerTextColor">?android:attr/textColorSecondary</item>
         <item name="pickerSelectedColor">@android:color/system_accent1_300</item>
     </style>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index eb3b0d4..5d6dcdb 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -22,9 +22,4 @@
     <!-- PhotoPicker -->
     <color name="picker_background_color">#202124</color>
     <color name="picker_drag_bar_color">#686868</color>
-
-    <!-- PhotoPicker Profile Button -->
-    <color name="picker_profile_disabled_button_content_color">#E3E3E3</color>
-    <color name="picker_profile_disabled_button_background_color">#DADADA</color>
-
 </resources>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index dd8eacc..3975c1b 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -37,13 +37,15 @@
 
     <style name="PickerMaterialTheme" parent="@style/Theme.MaterialComponents.DayNight.NoActionBar">
         <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
-        <item name="pickerChipBackgroundColor">@color/picker_background_color</item>
+        <item name="pickerTabBackgroundColor">@color/picker_background_color</item>
         <item name="pickerHighlightColor">?android:attr/colorAccent</item>
         <item name="pickerHighlightTextColor">#202124</item>
         <item name="pickerProfileButtonColor">#1F1F1F</item>
+        <item name="pickerDisabledProfileButtonColor">#1FE3E3E3</item>
         <item name="pickerProfileButtonTextColor">#A8C7FA</item>
-        <item name="pickerSelectedChipBackgroundColor">#3D8AB4F8</item>
-        <item name="pickerSelectedChipTextColor">#8AB4F8</item>
+        <item name="pickerDisabledProfileButtonTextColor">#61E3E3E3</item>
+        <item name="pickerSelectedTabBackgroundColor">#3D8AB4F8</item>
+        <item name="pickerSelectedTabTextColor">#8AB4F8</item>
         <item name="pickerTextColor">?android:attr/textColorSecondary</item>
         <item name="pickerSelectedColor">?android:attr/colorAccent</item>
     </style>
diff --git a/res/values-v31/dimens.xml b/res/values-v31/dimens.xml
index 2fc4f2e..0c0ad5e 100644
--- a/res/values-v31/dimens.xml
+++ b/res/values-v31/dimens.xml
@@ -16,7 +16,10 @@
 
 <resources>
 
-    <dimen name="picker_chip_radius">12dp</dimen>
+    <dimen name="picker_tab_radius">12dp</dimen>
+    <dimen name="picker_tab_height">36dp</dimen>
+    <dimen name="picker_tab_width">96dp</dimen>
+    <dimen name="picker_tab_min_width">104dp</dimen>
 
     <dimen name="picker_bottom_bar_size">72dp</dimen>
 
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
index 0b10d19..908ce72 100644
--- a/res/values-v31/styles.xml
+++ b/res/values-v31/styles.xml
@@ -18,13 +18,15 @@
 
     <style name="PickerMaterialTheme" parent="@style/Theme.Material3.DayNight.NoActionBar">
         <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
-        <item name="pickerChipBackgroundColor">?android:attr/colorBackground</item>
+        <item name="pickerTabBackgroundColor">?android:attr/colorBackground</item>
         <item name="pickerHighlightColor">@android:color/system_accent1_100</item>
         <item name="pickerHighlightTextColor">?android:attr/textColorPrimary</item>
         <item name="pickerProfileButtonColor">@android:color/system_accent1_100</item>
+        <item name="pickerDisabledProfileButtonColor">?android:attr/colorBackground</item>
         <item name="pickerProfileButtonTextColor">?android:attr/textColorPrimary</item>
-        <item name="pickerSelectedChipBackgroundColor">@android:color/system_accent1_100</item>
-        <item name="pickerSelectedChipTextColor">?android:attr/textColorPrimary</item>
+        <item name="pickerDisabledProfileButtonTextColor">?android:attr/textColorTertiaryInverse</item>
+        <item name="pickerSelectedTabBackgroundColor">@android:color/system_accent1_100</item>
+        <item name="pickerSelectedTabTextColor">?android:attr/textColorPrimary</item>
         <item name="pickerTextColor">?android:attr/textColorPrimary</item>
         <item name="pickerSelectedColor">@android:color/system_accent1_600</item>
     </style>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 5557364..bf6d054 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -15,8 +15,8 @@
 -->
 <resources>
 
-    <!-- The default background color of the chip. -->
-    <attr name="pickerChipBackgroundColor" format="reference|color" />
+    <!-- The default background color of the tab. -->
+    <attr name="pickerTabBackgroundColor" format="reference|color" />
 
     <!-- The highlight color of the photo picker. E.g. Add button -->
     <attr name="pickerHighlightColor" format="reference|color" />
@@ -27,14 +27,20 @@
     <!-- The background color of the profile button. -->
     <attr name="pickerProfileButtonColor" format="reference|color" />
 
+    <!-- The background color of the profile button when disabled. -->
+    <attr name="pickerDisabledProfileButtonColor" format="reference|color" />
+
     <!-- The text color of the profile button. -->
     <attr name="pickerProfileButtonTextColor" format="reference|color" />
 
-    <!-- The selected background color of the chip. -->
-    <attr name="pickerSelectedChipBackgroundColor" format="reference|color" />
+    <!-- The text color of the profile button when disabled. -->
+    <attr name="pickerDisabledProfileButtonTextColor" format="reference|color" />
 
-    <!-- The selected text color of the chip. -->
-    <attr name="pickerSelectedChipTextColor" format="reference|color" />
+    <!-- The selected background color of the tab. -->
+    <attr name="pickerSelectedTabBackgroundColor" format="reference|color" />
+
+    <!-- The selected text color of the tab. -->
+    <attr name="pickerSelectedTabTextColor" format="reference|color" />
 
     <!-- The most prominent text color of the photo picker.  -->
     <attr name="pickerTextColor" format="reference|color" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index e934274..bd346cf 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,8 +36,4 @@
     <color name="preview_highlight_color">#8AB4F8</color>
     <color name="preview_default_grey">#202124</color>
     <color name="preview_background_color">@android:color/black</color>
-
-    <!-- PhotoPicker Profile Button -->
-    <color name="picker_profile_disabled_button_content_color">#1F1F1F</color>
-    <color name="picker_profile_disabled_button_background_color">#DADADA</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 80e76e2..78b8367 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -21,7 +21,7 @@
     <dimen name="dialog_space">20dp</dimen>
 
     <!-- PhotoPicker -->
-    <dimen name="picker_top_corner_radius">16dp</dimen>
+    <dimen name="picker_top_corner_radius">28dp</dimen>
     <dimen name="picker_photo_size">118dp</dimen>
     <dimen name="picker_album_size">156dp</dimen>
 
@@ -31,6 +31,7 @@
     <dimen name="picker_bottom_bar_elevation">8dp</dimen>
 
     <dimen name="picker_viewselected_icon_padding">10dp</dimen>
+    <dimen name="picker_viewselected_icon_size">24dp</dimen>
 
     <dimen name="picker_item_check_size">24dp</dimen>
     <dimen name="picker_item_check_margin">6dp</dimen>
@@ -53,12 +54,15 @@
 
     <dimen name="picker_photo_item_spacing">3dp</dimen>
 
-    <dimen name="picker_chip_text_size">16sp</dimen>
-    <dimen name="picker_chip_touch_size">48dp</dimen>
-    <dimen name="picker_chip_radius">16dp</dimen>
-    <dimen name="picker_chip_horizontal_gap">4dp</dimen>
+    <dimen name="picker_tab_text_size">14sp</dimen>
+    <dimen name="picker_tab_touch_size">48dp</dimen>
+    <dimen name="picker_tab_radius">16dp</dimen>
+    <dimen name="picker_tab_height">32dp</dimen>
+    <dimen name="picker_tab_width">80dp</dimen>
+    <dimen name="picker_tab_min_width">88dp</dimen>
+    <dimen name="picker_tab_horizontal_gap">4dp</dimen>
 
-    <dimen name="picker_drag_margin_top">8dp</dimen>
+    <dimen name="picker_drag_margin_top">16dp</dimen>
     <dimen name="picker_drag_margin_bottom">12dp</dimen>
 
     <dimen name="picker_privacy_text_margin_top">8dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 11da0e2..c9c2599 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -85,9 +85,15 @@
     <!-- Deselect button for PhotoPicker. [CHAR LIMIT=30] -->
     <string name="deselect">Deselect</string>
 
+    <!-- Deselected check button for PhotoPicker. [CHAR LIMIT=30] -->
+    <string name="deselected">Deselected</string>
+
     <!-- Select button for PhotoPicker. [CHAR LIMIT=30] -->
     <string name="select">Select</string>
 
+    <!-- Selected check button for PhotoPicker. [CHAR LIMIT=30] -->
+    <string name="selected">Selected</string>
+
     <!-- Select up to max label message for PhotoPicker. [CHAR LIMIT=30] -->
     <string name="select_up_to"> {count, plural,
         =1    {Select up to <xliff:g id="count" example="1">^1</xliff:g> item}
@@ -105,10 +111,10 @@
     <!-- PhotoPicker view selected action text. [CHAR LIMIT=80] -->
     <string name="picker_view_selected">View selected</string>
 
-    <!-- The text of the photos chip on the toolbar for PhotoPicker. [CHAR LIMIT=30] -->
+    <!-- The text of the photos tab on the toolbar for PhotoPicker. [CHAR LIMIT=30] -->
     <string name="picker_photos">Photos</string>
 
-    <!-- The text of the albums chip on the toolbar for PhotoPicker. [CHAR LIMIT=30] -->
+    <!-- The text of the albums tab on the toolbar for PhotoPicker. [CHAR LIMIT=30] -->
     <string name="picker_albums">Albums</string>
 
     <!-- The text of the switching work/personal profile in PhotoPicker. [CHAR LIMIT=80] -->
@@ -157,6 +163,21 @@
     <!-- Special format text in preview screen for Motion Photo. [CHAR LIMIT=30] -->
     <string name="picker_motion_photo_text">Motion Photo</string>
 
+    <!-- Content description of when a photo/video was taken on [CHAR LIMIT=NONE] -->
+    <string name="picker_item_content_desc"><xliff:g id="item_name" example="Photo">%1$s</xliff:g> taken on <xliff:g id="time" example="Jul 7, 2020, 12:00:00 AM">%2$s</xliff:g></string>
+
+    <!-- Title of the picker photo item [CHAR LIMIT=40] -->
+    <string name="picker_photo">Photo</string>
+
+    <!-- Title of the picker video item [CHAR LIMIT=40] -->
+    <string name="picker_video">Video</string>
+
+    <!-- Title of the picker GIF item [CHAR LIMIT=40] -->
+    <string name="picker_gif">GIF</string>
+
+    <!-- Title of the picker motion photo item [CHAR LIMIT=60] -->
+    <string name="picker_motion_photo">Motion Photo</string>
+
     <!-- ========================= BEGIN AUTO-GENERATED BY gen_strings.py ========================= -->
 
     <!-- ========================= WRITE STRINGS ========================= -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e52a763..249556c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -68,6 +68,7 @@
     <style name="MaterialAlertDialogIconStyle"
            parent="@style/MaterialAlertDialog.MaterialComponents.Title.Icon.CenterStacked">
         <item name="android:tint">?android:attr/colorAccent</item>
+        <item name="android:importantForAccessibility">no</item>
         <item name="android:layout_width">@dimen/picker_profile_dialog_icon_width</item>
         <item name="android:layout_height">@dimen/picker_profile_dialog_icon_height</item>
     </style>
@@ -84,13 +85,15 @@
 
     <style name="PickerMaterialTheme" parent="@style/Theme.MaterialComponents.DayNight.NoActionBar">
         <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
-        <item name="pickerChipBackgroundColor">@color/picker_background_color</item>
+        <item name="pickerTabBackgroundColor">@color/picker_background_color</item>
         <item name="pickerHighlightColor">?android:attr/colorAccent</item>
         <item name="pickerHighlightTextColor">@android:color/white</item>
         <item name="pickerProfileButtonColor">#E8F0FE</item>
+        <item name="pickerDisabledProfileButtonColor">#DADADA</item>
         <item name="pickerProfileButtonTextColor">#0B57D0</item>
-        <item name="pickerSelectedChipBackgroundColor">#E8F0FE</item>
-        <item name="pickerSelectedChipTextColor">#185ABC</item>
+        <item name="pickerDisabledProfileButtonTextColor">#1F1F1F</item>
+        <item name="pickerSelectedTabBackgroundColor">#E8F0FE</item>
+        <item name="pickerSelectedTabTextColor">#185ABC</item>
         <item name="pickerTextColor">?android:attr/textColorPrimary</item>
         <item name="pickerSelectedColor">?android:attr/colorAccent</item>
     </style>
diff --git a/res/values/styles_text.xml b/res/values/styles_text.xml
index 7368b07..bb8e245 100644
--- a/res/values/styles_text.xml
+++ b/res/values/styles_text.xml
@@ -42,9 +42,10 @@
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="PickerChipTextAppearance"
-           parent="@android:style/TextAppearance.DeviceDefault.Widget.Button">
-        <item name="android:textSize">@dimen/picker_chip_text_size</item>
+    <style name="PickerTabTextAppearance"
+           parent="@android:style/TextAppearance.DeviceDefault.Widget.TabWidget">
+        <item name="android:textSize">@dimen/picker_tab_text_size</item>
+        <item name="android:textAllCaps">false</item>
     </style>
 
     <style name="PickerButtonTextAppearance"
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 5612e2e..5a35ab6 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -227,6 +227,7 @@
 import com.android.providers.media.photopicker.PickerSyncController;
 import com.android.providers.media.photopicker.data.ExternalDbFacade;
 import com.android.providers.media.photopicker.data.PickerDbFacade;
+import com.android.providers.media.photopicker.data.model.Category;
 import com.android.providers.media.playlist.Playlist;
 import com.android.providers.media.scan.MediaScanner;
 import com.android.providers.media.scan.ModernMediaScanner;
@@ -368,6 +369,13 @@
     private static final int NON_HIDDEN_CACHE_SIZE = 50;
 
     /**
+     * This is required as idle maintenance maybe stopped anytime; we do not want to query
+     * and accumulate values to update for a long time, instead we want to batch query and update
+     * by a limited number.
+     */
+    private static final int IDLE_MAINTENANCE_ROWS_LIMIT = 1000;
+
+    /**
      * Where clause to match pending files from FUSE. Pending files from FUSE will not have
      * PATTERN_PENDING_FILEPATH_FOR_SQL pattern.
      */
@@ -1215,9 +1223,6 @@
         // Forget any stale volumes
         deleteStaleVolumes(signal);
 
-        // Populate _SPECIAL_FORMAT column for files which have column value as NULL
-        detectSpecialFormat(signal);
-
         final long itemCount = mExternalDatabase.runWithTransaction((db) -> {
             return DatabaseHelper.getItemCount(db);
         });
@@ -1225,6 +1230,9 @@
         // Cleaning media files for users that have been removed
         cleanMediaFilesForRemovedUser(signal);
 
+        // Populate _SPECIAL_FORMAT column for files which have column value as NULL
+        detectSpecialFormat(signal);
+
         final long durationMillis = (SystemClock.elapsedRealtime() - startTime);
         Metrics.logIdleMaintenance(MediaStore.VOLUME_EXTERNAL, itemCount,
                 durationMillis, staleThumbnails, deletedExpiredMedia);
@@ -1334,49 +1342,84 @@
     }
 
     private void updateSpecialFormatColumn(SQLiteDatabase db, @NonNull CancellationSignal signal) {
-        try (Cursor c = queryForPendingSpecialFormatColumns(db, signal)) {
-            while (c.moveToNext() && !signal.isCanceled()) {
-                final long id = c.getLong(0);
-                final String path = c.getString(1);
-                final ContentValues contentValues = getContentValuesForSpecialFormat(path);
-                if (contentValues == null) {
-                    continue;
-                }
-                final String whereClause = MediaColumns._ID + "=?";
-                final String[] whereArgs = new String[]{String.valueOf(id)};
-                db.update("files", contentValues, whereClause, whereArgs);
-            }
+        // This is to ensure we only do a bounded iteration over the rows as updates can fail, and
+        // we don't want to keep running the query/update indefinitely.
+        final int totalRowsToUpdate = getPendingSpecialFormatRowsCount(db,signal);
+        for (int i = 0 ; i < totalRowsToUpdate ; i += IDLE_MAINTENANCE_ROWS_LIMIT) {
+            updateSpecialFormatForLimitedRows(db, signal);
         }
     }
 
-    private ContentValues getContentValuesForSpecialFormat(String path) {
-        ContentValues contentValues = new ContentValues();
+    private int getPendingSpecialFormatRowsCount(SQLiteDatabase db,
+            @NonNull CancellationSignal signal) {
+        try (Cursor c = queryForPendingSpecialFormatColumns(db, /* limit */ null, signal)) {
+            if (c == null) {
+                return 0;
+            }
+            return c.getCount();
+        }
+    }
+
+    private void updateSpecialFormatForLimitedRows(SQLiteDatabase db,
+            @NonNull CancellationSignal signal) {
+        // Accumulate all the new SPECIAL_FORMAT updates with their ids
+        ArrayMap<Long, Integer> newSpecialFormatValues = new ArrayMap<>();
+        final String limit = String.valueOf(IDLE_MAINTENANCE_ROWS_LIMIT);
+        try (Cursor c = queryForPendingSpecialFormatColumns(db, limit, signal)) {
+            while (c.moveToNext() && !signal.isCanceled()) {
+                final long id = c.getLong(0);
+                final String path = c.getString(1);
+                newSpecialFormatValues.put(id, getSpecialFormatValue(path));
+            }
+        }
+
+        // Now, update all the new SPECIAL_FORMAT values.
+        final ContentValues values = new ContentValues();
+        int count = 0;
+        for (long id: newSpecialFormatValues.keySet()) {
+            if (signal.isCanceled()) {
+                return;
+            }
+
+            values.clear();
+            values.put(_SPECIAL_FORMAT, newSpecialFormatValues.get(id));
+            final String whereClause = MediaColumns._ID + "=?";
+            final String[] whereArgs = new String[]{String.valueOf(id)};
+            if (db.update("files", values, whereClause, whereArgs) == 1) {
+                count++;
+            } else {
+                Log.e(TAG, "Unable to update _SPECIAL_FORMAT for id = " + id);
+            }
+        }
+        Log.d(TAG, "Updated _SPECIAL_FORMAT for " + count + " items");
+    }
+
+    private int getSpecialFormatValue(String path) {
         final File file = new File(path);
         if (!file.exists()) {
-            // Ignore if the file does not exist. This may happen if a file was
-            // inserted and then not opened, or if a file was deleted but db is not
-            // updated yet.
-            return null;
+            // We always update special format to none if the file is not found or there is an
+            // error, this is so that we do not repeat over the same column again and again.
+            return _SPECIAL_FORMAT_NONE;
         }
+
         try {
-            contentValues.put(_SPECIAL_FORMAT, SpecialFormatDetector.detect(file));
+            return SpecialFormatDetector.detect(file);
         } catch (Exception e) {
             // we tried our best, no need to run special detection again and again if it
             // throws exception once, it is likely to do so everytime.
             Log.d(TAG, "Failed to detect special format for file: " + file, e);
-            contentValues.put(_SPECIAL_FORMAT, _SPECIAL_FORMAT_NONE);
+            return _SPECIAL_FORMAT_NONE;
         }
-        return contentValues;
     }
 
-    private Cursor queryForPendingSpecialFormatColumns(SQLiteDatabase db,
+    private Cursor queryForPendingSpecialFormatColumns(SQLiteDatabase db, String limit,
             @NonNull CancellationSignal signal) {
         // Run special detection for images only
         final String selection = _SPECIAL_FORMAT + " IS NULL AND "
                 + MEDIA_TYPE + "=" + MEDIA_TYPE_IMAGE;
         final String[] projection = new String[] { MediaColumns._ID, MediaColumns.DATA };
         return db.query(/* distinct */ true, "files", projection, selection, null, null, null,
-                null, null, signal);
+                null, limit, signal);
     }
 
     /**
@@ -1971,8 +2014,8 @@
 
         final Uri uri = Uri.parse("content://media/picker/" + userId + "/" + authority + "/media/"
                 + mediaId);
-        try (Cursor cursor =  mPickerUriResolver.query(uri, projection, /* queryArgs */ null,
-                        /* signal */ null, 0, android.os.Process.myUid())) {
+        try (Cursor cursor =  mPickerUriResolver.query(uri, projection, /* callingUid */0,
+                android.os.Process.myUid())) {
             if (cursor != null && cursor.moveToFirst()) {
                 final int sizeBytesIdx = cursor.getColumnIndex(MediaStore.PickerMediaColumns.SIZE);
 
@@ -3164,8 +3207,8 @@
     private Cursor queryInternal(Uri uri, String[] projection, Bundle queryArgs,
             CancellationSignal signal, boolean forSelf) throws FallbackException {
         if (isPickerUri(uri)) {
-            return mPickerUriResolver.query(uri, projection, queryArgs, signal,
-                    mCallingIdentity.get().pid, mCallingIdentity.get().uid);
+            return mPickerUriResolver.query(uri, projection, mCallingIdentity.get().pid,
+                    mCallingIdentity.get().uid);
         }
 
         final String volumeName = getVolumeName(uri);
@@ -3219,6 +3262,10 @@
 
         // TODO(b/195008831): Add test to verify that apps can't access
         if (table == PICKER_INTERNAL_MEDIA) {
+            String albumId = queryArgs.getString(MediaStore.QUERY_ARG_ALBUM_ID);
+            if (!TextUtils.isEmpty(albumId) && !Category.CATEGORY_FAVORITES.equals(albumId)) {
+                mPickerSyncController.syncAlbumMedia(albumId);
+            }
             return mPickerDataLayer.fetchMedia(queryArgs);
         } else if (table == PICKER_INTERNAL_ALBUMS) {
             return mPickerDataLayer.fetchAlbums(queryArgs);
diff --git a/src/com/android/providers/media/PickerUriResolver.java b/src/com/android/providers/media/PickerUriResolver.java
index 14e1e21..025745d 100644
--- a/src/com/android/providers/media/PickerUriResolver.java
+++ b/src/com/android/providers/media/PickerUriResolver.java
@@ -21,7 +21,6 @@
 import static com.android.providers.media.util.FileUtils.toFuseFile;
 
 import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -29,8 +28,6 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -40,18 +37,13 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.modules.utils.build.SdkLevel;
-import com.android.providers.media.photopicker.data.ExternalDbFacade;
 import com.android.providers.media.photopicker.data.PickerDbFacade;
 import com.android.providers.media.photopicker.data.model.UserId;
-import com.android.providers.media.photopicker.data.PickerDbFacade;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -101,20 +93,10 @@
             Log.e(TAG, "No item at " + uri, e);
             throw new FileNotFoundException("No item at " + uri);
         }
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            if (canHandleUriInUser(uri)) {
-                return openPickerFile(uri);
-            }
-            return resolver.openFile(uri, mode, signal);
+        if (canHandleUriInUser(uri)) {
+            return openPickerFile(uri);
         }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            uri = getRedactedFileUriFromPickerUri(uri, resolver);
-            return resolver.openFile(uri, "r", signal);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return resolver.openFile(uri, mode, signal);
     }
 
     public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts,
@@ -130,29 +112,18 @@
             Log.e(TAG, "No item at " + uri, e);
             throw new FileNotFoundException("No item at " + uri);
         }
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            if (canHandleUriInUser(uri)) {
-                return new AssetFileDescriptor(openPickerFile(uri), 0,
-                        AssetFileDescriptor.UNKNOWN_LENGTH);
-            }
-            return resolver.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
+        if (canHandleUriInUser(uri)) {
+            return new AssetFileDescriptor(openPickerFile(uri), 0,
+                    AssetFileDescriptor.UNKNOWN_LENGTH);
         }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            uri = getRedactedFileUriFromPickerUri(uri, resolver);
-            return resolver.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return resolver.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
     }
 
-    public Cursor query(Uri uri, String[] projection, Bundle queryArgs, CancellationSignal signal,
-            int callingPid, int callingUid) {
+    public Cursor query(Uri uri, String[] projection, int callingPid, int callingUid) {
         checkUriPermission(uri, callingPid, callingUid);
 
         try {
-            return queryInternal(uri, projection, queryArgs, signal);
+            return queryInternal(uri, projection);
         } catch (IllegalArgumentException e) {
             // This is to be consistent with MediaProvider, it returns an empty cursor if the row
             // does not exist.
@@ -161,47 +132,31 @@
         }
     }
 
-    // TODO(b/191362529): Restrict projection values when we start querying picker db.
-    // Add PickerColumns and add checks for projection.
-    private Cursor queryInternal(Uri uri, String[] projection, Bundle queryArgs,
-            CancellationSignal signal) {
+    private Cursor queryInternal(Uri uri, String[] projection) {
         final ContentResolver resolver = getContentResolverForUserId(uri);
 
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            if (canHandleUriInUser(uri)) {
-                if (projection == null || projection.length == 0) {
-                    projection = new String[] {
+        if (canHandleUriInUser(uri)) {
+            if (projection == null || projection.length == 0) {
+                projection = new String[]{
                         MediaStore.PickerMediaColumns.DISPLAY_NAME,
                         MediaStore.PickerMediaColumns.DATA,
                         MediaStore.PickerMediaColumns.MIME_TYPE,
                         MediaStore.PickerMediaColumns.DATE_TAKEN,
                         MediaStore.PickerMediaColumns.SIZE,
                         MediaStore.PickerMediaColumns.DURATION_MILLIS
-                    };
-                }
-
-                return queryPickerUri(uri, projection);
+                };
             }
-            return resolver.query(uri, /* projection */ null, /* queryArgs */ null,
-                    /* cancellationSignal */ null);
-        }
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            // Support query similar to as we support for redacted mediastore file uris.
-            return resolver.query(getRedactedFileUriFromPickerUri(uri, resolver), projection,
-                    queryArgs, signal);
-        } finally {
-            Binder.restoreCallingIdentity(token);
+            return queryPickerUri(uri, projection);
         }
+        return resolver.query(uri, /* projection */ null, /* queryArgs */ null,
+                /* cancellationSignal */ null);
     }
 
     public String getType(@NonNull Uri uri) {
         // There's no permission check because ContentProviders allow anyone to check the mimetype
         // of a URI
-
-        try (Cursor cursor = queryInternal(uri, new String[]{MediaStore.MediaColumns.MIME_TYPE},
-                        /* queryArgs */ null, /* signal */ null)) {
+        try (Cursor cursor = queryInternal(uri, new String[]{MediaStore.MediaColumns.MIME_TYPE})) {
             if (cursor != null && cursor.getCount() == 1 && cursor.moveToFirst()) {
                 return getCursorString(cursor,
                         CloudMediaProviderContract.MediaColumns.MIME_TYPE);
@@ -312,33 +267,6 @@
         return builder;
     }
 
-    /**
-     * @return {@link MediaStore.Files} Uri that always redacts sensitive data
-     */
-    private Uri getRedactedFileUriFromPickerUri(Uri uri, ContentResolver contentResolver) {
-        // content://media/picker/<user-id>/<media-id>
-        final long id = Long.parseLong(uri.getPathSegments().get(2));
-        final Uri res = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL, id);
-        return getRedactedUri(contentResolver, res);
-    }
-
-    @VisibleForTesting
-    Uri getRedactedUri(ContentResolver contentResolver, Uri uri) {
-        if (SdkLevel.isAtLeastS()) {
-            return getRedactedUriFromMediaStoreAPI(contentResolver, uri);
-        } else {
-            // TODO (b/201994830): directly call redacted uri code logic or explore other solution.
-            // Devices running on Android R cannot call getRedacted() as the API is added in
-            // Android S.
-            return uri;
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.S)
-    private static Uri getRedactedUriFromMediaStoreAPI(ContentResolver contentResolver, Uri uri) {
-        return MediaStore.getRedactedUri(contentResolver, uri);
-    }
-
     @VisibleForTesting
     static int getUserId(Uri uri) {
         // content://media/picker/<user-id>/<media-id>/...
@@ -365,7 +293,7 @@
 
     @VisibleForTesting
     ContentResolver getContentResolverForUserId(Uri uri) {
-            final UserId userId = UserId.of(UserHandle.of(getUserId(uri)));
+        final UserId userId = UserId.of(UserHandle.of(getUserId(uri)));
         try {
             return userId.getContentResolver(mContext);
         } catch (NameNotFoundException e) {
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index 4386388..33746af 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -19,7 +19,6 @@
 import static com.android.providers.media.photopicker.data.PickerResult.getPickerResponseIntent;
 import static com.android.providers.media.photopicker.util.LayoutModeUtils.MODE_PHOTOS_TAB;
 
-import android.annotation.IntDef;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -38,10 +37,8 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.WindowInsetsController;
 import android.view.WindowManager;
@@ -58,21 +55,16 @@
 import com.android.providers.media.R;
 import com.android.providers.media.photopicker.data.Selection;
 import com.android.providers.media.photopicker.data.UserIdManager;
-import com.android.providers.media.photopicker.data.model.Category;
 import com.android.providers.media.photopicker.data.model.UserId;
-import com.android.providers.media.photopicker.ui.AlbumsTabFragment;
-import com.android.providers.media.photopicker.ui.PhotosTabFragment;
-import com.android.providers.media.photopicker.ui.PreviewFragment;
+import com.android.providers.media.photopicker.ui.TabContainerFragment;
 import com.android.providers.media.photopicker.util.LayoutModeUtils;
 import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
 
 import com.google.android.material.bottomsheet.BottomSheetBehavior;
 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
-import com.google.android.material.chip.Chip;
+import com.google.android.material.tabs.TabLayout;
 import com.google.common.collect.Lists;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -80,37 +72,22 @@
  * app does not get access to all photos/videos.
  */
 public class PhotoPickerActivity extends AppCompatActivity {
-
     private static final String TAG =  "PhotoPickerActivity";
-    private static final String EXTRA_TAB_CHIP_TYPE = "tab_chip_type";
-    private static final int TAB_CHIP_TYPE_PHOTOS = 0;
-    private static final int TAB_CHIP_TYPE_ALBUMS = 1;
-
     private static final float BOTTOM_SHEET_PEEK_HEIGHT_PERCENTAGE = 0.60f;
 
-    @IntDef(prefix = { "TAB_CHIP_TYPE" }, value = {
-            TAB_CHIP_TYPE_PHOTOS,
-            TAB_CHIP_TYPE_ALBUMS
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface TabChipType {}
-
     private PickerViewModel mPickerViewModel;
     private Selection mSelection;
-    private ViewGroup mTabChipContainer;
-    private Chip mPhotosTabChip;
-    private Chip mAlbumsTabChip;
     private BottomSheetBehavior mBottomSheetBehavior;
+    private View mBottomBar;
     private View mBottomSheetView;
     private View mFragmentContainerView;
     private View mDragBar;
     private View mPrivacyText;
+    private View mProfileButton;
+    private TabLayout mTabLayout;
     private Toolbar mToolbar;
     private CrossProfileListeners mCrossProfileListeners;
 
-    @TabChipType
-    private int mSelectedTabChipType;
-
     @ColorInt
     private int mDefaultBackgroundColor;
 
@@ -158,9 +135,10 @@
 
         mDragBar = findViewById(R.id.drag_bar);
         mPrivacyText = findViewById(R.id.privacy_text);
+        mBottomBar = findViewById(R.id.picker_bottom_bar);
+        mProfileButton = findViewById(R.id.profile_button);
 
-        mTabChipContainer = findViewById(R.id.chip_container);
-        initTabChips();
+        mTabLayout = findViewById(R.id.tab_layout);
         initBottomSheetBehavior();
         restoreState(savedInstanceState);
 
@@ -262,30 +240,11 @@
     @Override
     public void onSaveInstanceState(Bundle state) {
         super.onSaveInstanceState(state);
-        state.putInt(EXTRA_TAB_CHIP_TYPE, mSelectedTabChipType);
         saveBottomSheetState();
     }
 
     private void restoreState(Bundle savedInstanceState) {
         if (savedInstanceState != null) {
-            final int tabChipType = savedInstanceState.getInt(EXTRA_TAB_CHIP_TYPE,
-                    TAB_CHIP_TYPE_PHOTOS);
-            mSelectedTabChipType = tabChipType;
-            if (tabChipType == TAB_CHIP_TYPE_PHOTOS) {
-                if (PreviewFragment.get(getSupportFragmentManager()) == null) {
-                    onTabChipClick(mPhotosTabChip);
-                } else {
-                    // PreviewFragment is shown
-                    mPhotosTabChip.setSelected(true);
-                }
-            } else { // CHIP_TYPE_ALBUMS
-                if (PhotosTabFragment.get(getSupportFragmentManager()) == null) {
-                    onTabChipClick(mAlbumsTabChip);
-                } else {
-                    // PreviewFragment or PhotosTabFragment with category is shown
-                    mAlbumsTabChip.setSelected(true);
-                }
-            }
             restoreBottomSheetState();
         } else {
             setupInitialLaunchState();
@@ -294,25 +253,14 @@
 
     /**
      * Sets up states for the initial launch. This includes updating common layouts, selecting
-     * Photos tab chip and saving the current bottom sheet state for later.
+     * Photos tab item and saving the current bottom sheet state for later.
      */
     private void setupInitialLaunchState() {
         updateCommonLayouts(MODE_PHOTOS_TAB, /* title */ "");
-        onTabChipClick(mPhotosTabChip);
+        TabContainerFragment.show(getSupportFragmentManager());
         saveBottomSheetState();
     }
 
-    private static Chip generateTabChip(LayoutInflater inflater, ViewGroup parent, String title) {
-        final Chip chip = (Chip) inflater.inflate(R.layout.picker_chip_tab_header, parent, false);
-        chip.setText(title);
-        return chip;
-    }
-
-    private void initTabChips() {
-        initPhotosTabChip();
-        initAlbumsTabChip();
-    }
-
     private void initBottomSheetBehavior() {
         mBottomSheetView = findViewById(R.id.bottom_sheet);
         mBottomSheetBehavior = BottomSheetBehavior.from(mBottomSheetView);
@@ -397,46 +345,6 @@
         return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
     }
 
-    private void initPhotosTabChip() {
-        if (mPhotosTabChip == null) {
-            mPhotosTabChip = generateTabChip(getLayoutInflater(), mTabChipContainer,
-                    getString(R.string.picker_photos));
-            mTabChipContainer.addView(mPhotosTabChip);
-            mPhotosTabChip.setOnClickListener(this::onTabChipClick);
-            mPhotosTabChip.setTag(TAB_CHIP_TYPE_PHOTOS);
-        }
-    }
-
-    private void initAlbumsTabChip() {
-        if (mAlbumsTabChip == null) {
-            mAlbumsTabChip = generateTabChip(getLayoutInflater(), mTabChipContainer,
-                    getString(R.string.picker_albums));
-            mTabChipContainer.addView(mAlbumsTabChip);
-            mAlbumsTabChip.setOnClickListener(this::onTabChipClick);
-            mAlbumsTabChip.setTag(TAB_CHIP_TYPE_ALBUMS);
-        }
-    }
-
-    private void onTabChipClick(@NonNull View view) {
-        final int chipType = (int) view.getTag();
-        mSelectedTabChipType = chipType;
-
-        // Check whether the tabChip is already selected or not. If it is selected, do nothing
-        if (view.isSelected()) {
-            return;
-        }
-
-        if (chipType == TAB_CHIP_TYPE_PHOTOS) {
-            mPhotosTabChip.setSelected(true);
-            mAlbumsTabChip.setSelected(false);
-            PhotosTabFragment.show(getSupportFragmentManager(), Category.getDefaultCategory());
-        } else { // CHIP_TYPE_ALBUMS
-            mPhotosTabChip.setSelected(false);
-            mAlbumsTabChip.setSelected(true);
-            AlbumsTabFragment.show(getSupportFragmentManager());
-        }
-    }
-
     public void setResultAndFinishSelf() {
         setResult(Activity.RESULT_OK, getPickerResponseIntent(mSelection.canSelectMultiple(),
                 mSelection.getSelectedItems()));
@@ -463,6 +371,13 @@
         updateFragmentContainerViewPadding(mode);
         updateDragBarVisibility(mode);
         updatePrivacyTextVisibility(mode);
+        // The bottom bar and profile button are not shown on preview, hide them in preview. We
+        // handle the visibility of them in TabFragment. We don't need to make them shown in
+        // non-preview page here.
+        if (mode.isPreview) {
+            mBottomBar.setVisibility(View.GONE);
+            mProfileButton.setVisibility(View.GONE);
+        }
     }
 
     private void updateTitle(String title) {
@@ -470,19 +385,19 @@
     }
 
     /**
-     * Updates the icons and show/hide the tab chips with {@code shouldShowTabChips}.
+     * Updates the icons and show/hide the tab layout with {@code mode}.
      *
      * @param mode {@link LayoutModeUtils.Mode} which describes the layout mode to update.
      */
     private void updateToolbar(@NonNull LayoutModeUtils.Mode mode) {
         final boolean isPreview = mode.isPreview;
-        final boolean shouldShowTabChips = mode.isPhotosTabOrAlbumsTab;
-        // 1. Set the tabChip visibility
-        mTabChipContainer.setVisibility(shouldShowTabChips ? View.VISIBLE : View.GONE);
+        final boolean shouldShowTabLayout = mode.isPhotosTabOrAlbumsTab;
+        // 1. Set the tabLayout visibility
+        mTabLayout.setVisibility(shouldShowTabLayout ? View.VISIBLE : View.GONE);
 
         // 2. Set the toolbar color
         final ColorDrawable toolbarColor;
-        if (isPreview && !shouldShowTabChips) {
+        if (isPreview && !shouldShowTabLayout) {
             if (isOrientationLandscape()) {
                 // Toolbar in Preview will have transparent color in Landscape mode.
                 toolbarColor = new ColorDrawable(getColor(android.R.color.transparent));
@@ -497,7 +412,7 @@
 
         // 3. Set the toolbar icon.
         final Drawable icon;
-        if (shouldShowTabChips) {
+        if (shouldShowTabLayout) {
             icon = getDrawable(R.drawable.ic_close);
         } else {
             icon = getDrawable(R.drawable.ic_arrow_back);
@@ -505,6 +420,9 @@
             icon.setTint(isPreview ? Color.WHITE : mToolBarIconColor);
         }
         getSupportActionBar().setHomeAsUpIndicator(icon);
+        getSupportActionBar().setHomeActionContentDescription(
+                shouldShowTabLayout ? android.R.string.cancel
+                        : R.string.abc_action_bar_up_description);
     }
 
     /**
@@ -686,15 +604,14 @@
         }
 
         private void switchToPersonalProfileInitialLaunchState() {
+            final FragmentManager fragmentManager = getSupportFragmentManager();
+            // Clear all back stacks in FragmentManager
+            fragmentManager.popBackStackImmediate(/* name */ null,
+                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
             // We reset the state of the PhotoPicker as we do not want to make any
             // assumptions on the state of the PhotoPicker when it was in Work Profile mode.
             resetToPersonalProfile();
-
-            final FragmentManager fragmentManager = getSupportFragmentManager();
-            // This is important so that doing a back does not take back to work profile fragment
-            // state.
-            fragmentManager.popBackStack();
-            PhotosTabFragment.show(fragmentManager, Category.getDefaultCategory());
         }
 
         /**
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
index 23aa3e6..cd6172e 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerProvider.java
@@ -17,7 +17,9 @@
 package com.android.providers.media.photopicker;
 
 import static android.provider.CloudMediaProviderContract.EXTRA_LOOPING_PLAYBACK_ENABLED;
-import static android.provider.CloudMediaProvider.SurfaceEventCallback.PLAYBACK_EVENT_READY;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_READY;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_BUFFERING;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_COMPLETED;
 import static android.provider.CloudMediaProviderContract.MediaCollectionInfo;
 
 import android.annotation.DurationMillisLong;
@@ -54,6 +56,7 @@
 import com.google.android.exoplayer2.LoadControl;
 import com.google.android.exoplayer2.MediaItem;
 import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.State;
 import com.google.android.exoplayer2.analytics.AnalyticsCollector;
 import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
 import com.google.android.exoplayer2.source.ProgressiveMediaSource;
@@ -158,12 +161,12 @@
 
     @Override
     @Nullable
-    public SurfaceController onCreateSurfaceController(@Nullable Bundle config,
-            SurfaceEventCallback callback) {
+    public CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@Nullable Bundle config,
+            CloudMediaSurfaceEventCallback callback) {
         if (RemotePreviewHandler.isRemotePreviewEnabled()) {
             boolean enableLoop = config != null && config.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED,
                     false);
-            return new SurfaceControllerImpl(getContext(), enableLoop, callback);
+            return new CloudMediaSurfaceControllerImpl(getContext(), enableLoop, callback);
         }
         return null;
     }
@@ -182,7 +185,7 @@
                 Long.parseLong(mediaId));
     }
 
-    private static final class SurfaceControllerImpl extends SurfaceController {
+    private static final class CloudMediaSurfaceControllerImpl extends CloudMediaSurfaceController {
 
         // The minimum duration of media that the player will attempt to ensure is buffered at all
         // times.
@@ -203,13 +206,37 @@
                         BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS).build();
 
         private final Context mContext;
-        private final SurfaceEventCallback mCallback;
+        private final CloudMediaSurfaceEventCallback mCallback;
         private final Handler mHandler = new Handler(Looper.getMainLooper());
         private final boolean mEnableLoop;
+        private final Player.EventListener mEventListener = new Player.EventListener() {
+            @Override
+            public void onPlaybackStateChanged(@State int state) {
+                Log.d(TAG, "Received player event " + state);
+
+                switch (state) {
+                    case Player.STATE_READY:
+                        mCallback.onPlaybackEvent(mCurrentSurfaceId, PLAYBACK_EVENT_READY,
+                                null);
+                        return;
+                    case Player.STATE_BUFFERING:
+                        mCallback.onPlaybackEvent(mCurrentSurfaceId, PLAYBACK_EVENT_BUFFERING,
+                                null);
+                        return;
+                    case Player.STATE_ENDED:
+                        mCallback.onPlaybackEvent(mCurrentSurfaceId, PLAYBACK_EVENT_COMPLETED,
+                                null);
+                        return;
+                    default:
+                }
+            }
+        };
+
         private ExoPlayer mPlayer;
         private int mCurrentSurfaceId = -1;
 
-        SurfaceControllerImpl(Context context, boolean enableLoop, SurfaceEventCallback callback) {
+        CloudMediaSurfaceControllerImpl(Context context, boolean enableLoop,
+                CloudMediaSurfaceEventCallback callback) {
             mCallback = callback;
             mContext = context;
             mEnableLoop = enableLoop;
@@ -220,6 +247,9 @@
         public void onPlayerCreate() {
             mHandler.post(() -> {
                 mPlayer = createExoPlayer();
+                mPlayer.setRepeatMode(mEnableLoop ?
+                        Player.REPEAT_MODE_ONE : Player.REPEAT_MODE_OFF);
+                mPlayer.addListener(mEventListener);
                 Log.d(TAG, "Player created.");
             });
         }
@@ -227,6 +257,7 @@
         @Override
         public void onPlayerRelease() {
             mHandler.post(() -> {
+                mPlayer.removeListener(mEventListener);
                 mPlayer.release();
                 mPlayer = null;
                 Log.d(TAG, "Player released.");
@@ -238,8 +269,22 @@
                 @NonNull String mediaId) {
             mHandler.post(() -> {
                 try {
-                    mPlayer.setRepeatMode(mEnableLoop ?
-                            Player.REPEAT_MODE_ONE : Player.REPEAT_MODE_OFF);
+                    // onSurfaceCreated may get called while the player is already rendering on a
+                    // different surface. In that case, pause the player before preparing it for
+                    // rendering on the new surface.
+                    // Unfortunately, Exoplayer#stop doesn't seem to work here. If we call stop(),
+                    // as soon as the player becomes ready again, it automatically starts to play
+                    // the new media. The reason is that Exoplayer treats play/pause as calls to
+                    // the method Exoplayer#setPlayWhenReady(boolean) with true and false
+                    // respectively. So, if we don't pause(), then since the previous play() call
+                    // had set setPlayWhenReady to true, the player would start the playback as soon
+                    // as it gets ready with the new media item.
+                    if (mPlayer.isPlaying()) {
+                        mPlayer.pause();
+                    }
+
+                    mCurrentSurfaceId = surfaceId;
+
                     final Uri mediaUri =
                             Uri.parse(
                                     MediaStore.Files.getContentUri(
@@ -247,15 +292,12 @@
                                     + File.separator + mediaId);
                     mPlayer.setMediaItem(MediaItem.fromUri(mediaUri));
                     mPlayer.setVideoSurface(surface);
-                    mCurrentSurfaceId = surfaceId;
                     mPlayer.prepare();
 
-                    mCallback.onPlaybackEvent(surfaceId, PLAYBACK_EVENT_READY, null);
-
                     Log.d(TAG, "Surface prepared: " + surfaceId + ". Surface: " + surface
                             + ". MediaId: " + mediaId);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error preparing surface.", e);
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "Error preparing player with surface.", e);
                 }
             });
         }
diff --git a/src/com/android/providers/media/photopicker/PickerSyncController.java b/src/com/android/providers/media/photopicker/PickerSyncController.java
index f392608..628b34e 100644
--- a/src/com/android/providers/media/photopicker/PickerSyncController.java
+++ b/src/com/android/providers/media/photopicker/PickerSyncController.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.media.photopicker;
 
+import static android.provider.CloudMediaProviderContract.EXTRA_FILTER_ALBUM;
 import static android.provider.CloudMediaProviderContract.EXTRA_SYNC_GENERATION;
 import static android.provider.CloudMediaProviderContract.EXTRA_PAGE_TOKEN;
 import static android.provider.CloudMediaProviderContract.MediaCollectionInfo;
@@ -73,15 +74,19 @@
             PickerDbFacade.getDefaultPickerDbSyncDelayMs();
 
     private static final int SYNC_TYPE_NONE = 0;
-    private static final int SYNC_TYPE_INCREMENTAL = 1;
-    private static final int SYNC_TYPE_FULL = 2;
-    private static final int SYNC_TYPE_RESET = 3;
+    private static final int SYNC_TYPE_MEDIA_INCREMENTAL = 1;
+    private static final int SYNC_TYPE_MEDIA_FULL = 2;
+    private static final int SYNC_TYPE_MEDIA_RESET = 3;
+    private static final int SYNC_TYPE_ALBUM_MEDIA_RESET = 4;
+    private static final int SYNC_TYPE_ALBUM_MEDIA_FULL = 5;
 
     @IntDef(flag = false, prefix = { "SYNC_TYPE_" }, value = {
                 SYNC_TYPE_NONE,
-                SYNC_TYPE_INCREMENTAL,
-                SYNC_TYPE_FULL,
-                SYNC_TYPE_RESET,
+            SYNC_TYPE_MEDIA_INCREMENTAL,
+            SYNC_TYPE_MEDIA_FULL,
+            SYNC_TYPE_MEDIA_RESET,
+            SYNC_TYPE_ALBUM_MEDIA_RESET,
+            SYNC_TYPE_ALBUM_MEDIA_FULL
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface SyncType {}
@@ -129,15 +134,11 @@
      * Syncs the local and currently enabled cloud {@link CloudMediaProvider} instances
      */
     public void syncAllMedia() {
-        if (!PickerDbFacade.isPickerDbEnabled()) {
-            return;
-        }
-
-        syncProvider(mLocalProvider);
+        syncAllMediaFromProvider(mLocalProvider);
 
         synchronized (mLock) {
             final String cloudProvider = mCloudProviderInfo.authority;
-            syncProvider(cloudProvider);
+            syncAllMediaFromProvider(cloudProvider);
 
             // Set the latest cloud provider on the facade
             mDbFacade.setCloudProvider(cloudProvider);
@@ -145,6 +146,22 @@
     }
 
     /**
+     * Syncs album media from the local and currently enabled cloud {@link CloudMediaProvider}
+     * instances
+     */
+    public void syncAlbumMedia(String albumId) {
+        syncAlbumMediaFromProvider(mLocalProvider, albumId);
+
+        synchronized (mLock) {
+            final String cloudProvider = mCloudProviderInfo.authority;
+            syncAlbumMediaFromProvider(cloudProvider, albumId);
+            // Should be a no-op. Cloud provider should already be set on the facade before an
+            // Album Media is fetched.
+            mDbFacade.setCloudProvider(cloudProvider);
+        }
+    }
+
+    /**
      * Returns the supported cloud {@link CloudMediaProvider} infos.
      */
     public CloudProviderInfo getCloudProviderInfo(String authority) {
@@ -287,12 +304,33 @@
         BackgroundThread.getHandler().postDelayed(this::syncAllMedia, mSyncDelayMs);
     }
 
+
+    private void syncAlbumMediaFromProvider(String authority, String albumId) {
+        final SyncRequestParams params = getSyncAlbumRequestParams(authority);
+        switch (params.syncType) {
+            case SYNC_TYPE_ALBUM_MEDIA_RESET:
+                executeSyncAlbumReset(authority, albumId);
+                return;
+            case SYNC_TYPE_ALBUM_MEDIA_FULL:
+                executeSyncAlbumReset(authority, albumId);
+                final Bundle queryArgs = new Bundle();
+                queryArgs.putString(EXTRA_FILTER_ALBUM, albumId);
+                executeSyncAddAlbum(authority, albumId, queryArgs /* queryArgs */);
+                return;
+            case SYNC_TYPE_NONE:
+                return;
+            default:
+                throw new IllegalArgumentException(
+                        "Unexpected sync type: " + params.syncType + " for album media");
+        }
+    }
+
     // TODO(b/190713331): Check extra_pages and extra_honored_args
-    private void syncProvider(String authority) {
+    private void syncAllMediaFromProvider(String authority) {
         final SyncRequestParams params = getSyncRequestParams(authority);
 
         switch (params.syncType) {
-            case SYNC_TYPE_RESET:
+            case SYNC_TYPE_MEDIA_RESET:
                 // Odd! Can only happen if provider gave us unexpected MediaCollectionInfo
                 // We reset the cloud media in the picker db
                 executeSyncReset(authority);
@@ -301,14 +339,14 @@
                 // we force a full sync
                 resetCachedMediaCollectionInfo(authority);
                 return;
-            case SYNC_TYPE_FULL:
+            case SYNC_TYPE_MEDIA_FULL:
                 executeSyncReset(authority);
                 executeSyncAdd(authority, new Bundle() /* queryArgs */);
 
                 // Commit sync position
                 cacheMediaCollectionInfo(authority, params.latestMediaCollectionInfo);
                 return;
-            case SYNC_TYPE_INCREMENTAL:
+            case SYNC_TYPE_MEDIA_INCREMENTAL:
                 final Bundle queryArgs = new Bundle();
                 queryArgs.putLong(EXTRA_SYNC_GENERATION, params.syncGeneration);
 
@@ -338,6 +376,18 @@
         }
     }
 
+    private void executeSyncAlbumReset(String authority, String albumId) {
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mDbFacade.beginResetAlbumMediaOperation(authority, albumId)) {
+            final int writeCount = operation.execute(null /* cursor */);
+            operation.setSuccess();
+            Log.i(TAG, "SyncResetAlbum. Authority: " + authority + ". AlbumId: " + albumId
+                    + ". Result count: " + writeCount);
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Failed to execute SyncReset.", e);
+        }
+    }
+
     private void executeSyncAdd(String authority, Bundle queryArgs) {
         final Uri uri = getMediaUri(authority);
         Log.i(TAG, "Executing SyncAdd with authority: " + authority);
@@ -349,6 +399,19 @@
         }
     }
 
+    private void executeSyncAddAlbum(String authority, String albumId, Bundle queryArgs) {
+        final Uri uri = getMediaUri(authority);
+        Log.i(TAG,
+                "Executing SyncAddAlbum with authority: " + authority + "and albumId: " + albumId);
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mDbFacade.beginAddAlbumMediaOperation(authority, albumId)) {
+            executePagedSync(uri, queryArgs, operation);
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Failed to execute SyncAddAlbum.", e);
+        }
+    }
+
+
     private void executeSyncRemove(String authority, Bundle queryArgs) {
         final Uri uri = getDeletedMediaUri(authority);
         Log.i(TAG, "Executing SyncRemove with authority: " + authority);
@@ -439,7 +502,7 @@
         if (authority == null) {
             // Only cloud authority can be null
             Log.d(TAG, "Fetching SyncRequestParams. Null cloud authority. Result: SYNC_TYPE_RESET");
-            return SyncRequestParams.forReset();
+            return SyncRequestParams.forResetMedia();
         }
 
         final Bundle cachedMediaCollectionInfo = getCachedMediaCollectionInfo(authority);
@@ -464,12 +527,12 @@
             // cloud provider
             Log.w(TAG, "SyncRequestParams. Authority: " + authority
                     + ". Result: SYNC_TYPE_RESET. Unexpected result: " + latestMediaCollectionInfo);
-            return SyncRequestParams.forReset();
+            return SyncRequestParams.forResetMedia();
         }
 
         if (!Objects.equals(latestCollectionId, cachedCollectionId)) {
             Log.d(TAG, "SyncRequestParams. Authority: " + authority + ". Result: SYNC_TYPE_FULL");
-            return SyncRequestParams.forFull(latestMediaCollectionInfo);
+            return SyncRequestParams.forFullMedia(latestMediaCollectionInfo);
         }
 
         if (cachedGeneration == latestGeneration) {
@@ -482,6 +545,22 @@
         return SyncRequestParams.forIncremental(cachedGeneration, latestMediaCollectionInfo);
     }
 
+
+    @SyncType
+    private SyncRequestParams getSyncAlbumRequestParams(String authority) {
+        if (authority == null) {
+            // Only cloud authority can be null
+            Log.d(TAG,
+                    "Fetching SyncRequestParams. Null cloud authority. Result: "
+                            + "SYNC_TYPE_ALBUM_MEDIA_RESET");
+            return SyncRequestParams.forResetAlbumMedia();
+        }
+
+        Log.d(TAG, "SyncRequestParams. Authority: " + authority
+                + ". Result: SYNC_TYPE_ALBUM_MEDIA_FULL");
+        return SyncRequestParams.forFullAlbumMedia();
+    }
+
     private String getPrefsKey(String authority, String key) {
         return (isLocal(authority) ? PREFS_KEY_LOCAL_PREFIX : PREFS_KEY_CLOUD_PREFIX) + key;
     }
@@ -495,6 +574,7 @@
                 /* cancellationSignal */ null);
     }
 
+    // TODO(b/195008834): Verify Cursor extras: extra_honored_args and extra_media_collection_id
     private void executePagedSync(Uri uri, Bundle queryArgs,
             PickerDbFacade.DbWriteOperation dbWriteOperation) {
         int cursorCount = 0;
@@ -585,8 +665,8 @@
     private static class SyncRequestParams {
         private static final SyncRequestParams SYNC_REQUEST_NONE =
                 new SyncRequestParams(SYNC_TYPE_NONE);
-        private static final SyncRequestParams SYNC_REQUEST_RESET =
-                new SyncRequestParams(SYNC_TYPE_RESET);
+        private static final SyncRequestParams SYNC_REQUEST_MEDIA_RESET =
+                new SyncRequestParams(SYNC_TYPE_MEDIA_RESET);
 
         private final int syncType;
         // Only valid for SYNC_TYPE_INCREMENTAL
@@ -609,17 +689,25 @@
             return SYNC_REQUEST_NONE;
         }
 
-        static SyncRequestParams forReset() {
-            return SYNC_REQUEST_RESET;
+        static SyncRequestParams forResetMedia() {
+            return SYNC_REQUEST_MEDIA_RESET;
         }
 
-        static SyncRequestParams forFull(Bundle latestMediaCollectionInfo) {
-            return new SyncRequestParams(SYNC_TYPE_FULL, /* generation */ 0,
+        static SyncRequestParams forResetAlbumMedia() {
+            return new SyncRequestParams(SYNC_TYPE_ALBUM_MEDIA_RESET);
+        }
+
+        static SyncRequestParams forFullMedia(Bundle latestMediaCollectionInfo) {
+            return new SyncRequestParams(SYNC_TYPE_MEDIA_FULL, /* generation */ 0,
                     latestMediaCollectionInfo);
         }
 
+        static SyncRequestParams forFullAlbumMedia() {
+            return new SyncRequestParams(SYNC_TYPE_ALBUM_MEDIA_FULL);
+        }
+
         static SyncRequestParams forIncremental(long generation, Bundle latestMediaCollectionInfo) {
-            return new SyncRequestParams(SYNC_TYPE_INCREMENTAL, generation,
+            return new SyncRequestParams(SYNC_TYPE_MEDIA_INCREMENTAL, generation,
                     latestMediaCollectionInfo);
         }
     }
diff --git a/src/com/android/providers/media/photopicker/data/ItemsProvider.java b/src/com/android/providers/media/photopicker/data/ItemsProvider.java
index 9c41579..c6c541a 100644
--- a/src/com/android/providers/media/photopicker/data/ItemsProvider.java
+++ b/src/com/android/providers/media/photopicker/data/ItemsProvider.java
@@ -16,8 +16,6 @@
 
 package com.android.providers.media.photopicker.data;
 
-import static com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentProvider;
@@ -26,38 +24,27 @@
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.Cursor;
-import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.CloudMediaProviderContract.AlbumColumns;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.PickerUriResolver;
-import com.android.providers.media.photopicker.PickerSyncController;
 import com.android.providers.media.photopicker.data.model.Category;
 import com.android.providers.media.photopicker.data.model.Category.CategoryColumns;
 import com.android.providers.media.photopicker.data.model.Item.ItemColumns;
 import com.android.providers.media.photopicker.data.model.UserId;
 
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * Provides image and video items from {@link MediaStore} collection to the Photo Picker.
  */
 public class ItemsProvider {
 
-    private static final String IMAGES_VIDEOS_WHERE_CLAUSE = "( " +
-            FileColumns.MEDIA_TYPE + " = " + FileColumns.MEDIA_TYPE_IMAGE + " OR "
-            + FileColumns.MEDIA_TYPE + " = " + FileColumns.MEDIA_TYPE_VIDEO + " )";
     private static final String TAG = ItemsProvider.class.getSimpleName();
 
     private final Context mContext;
@@ -97,21 +84,13 @@
         if (userId == null) {
             userId = UserId.CURRENT_USER;
         }
-
-        return getItemsInternal(category, offset, limit, mimeType, userId);
-    }
-
-    @Nullable
-    private Cursor getItemsInternal(@Nullable @Category.CategoryType String category,
-            int offset, int limit, @Nullable String mimeType, @NonNull UserId userId) throws
-            IllegalArgumentException {
         // Validate incoming params
         if (category != null && !Category.isValidCategory(category)) {
             throw new IllegalArgumentException("ItemsProvider does not support the given "
                     + "category: " + category);
         }
 
-        return query(ItemColumns.PROJECTION, category, mimeType, offset, limit, userId);
+        return queryMedia(limit, mimeType, category, userId);
     }
 
     /**
@@ -140,117 +119,7 @@
             userId = UserId.CURRENT_USER;
         }
 
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            return queryAlbums(mimeType, userId);
-        }
-
-        return buildCategoriesCursor(Category.CATEGORIES_LIST, mimeType, userId);
-    }
-
-    private Cursor buildCategoriesCursor(List<String> categories, @Nullable String mimeType,
-            @NonNull UserId userId) {
-        MatrixCursor c = new MatrixCursor(CategoryColumns.getAllColumns());
-
-        for (String category: categories) {
-            String[] categoryRow = getCategoryColumns(category, mimeType, userId);
-            if (categoryRow != null) {
-                c.addRow(categoryRow);
-            }
-        }
-
-        return c;
-    }
-
-    private String[] getCategoryColumns(@Category.CategoryType String category,
-            @Nullable String mimeType, @NonNull UserId userId) throws IllegalArgumentException {
-        if (!Category.isValidCategory(category)) {
-            throw new IllegalArgumentException("Category type not supported");
-        }
-
-        final String[] projection = new String[] { MediaColumns._ID };
-        Cursor c = query(projection, category, mimeType, 0, -1, userId);
-        // Send null if the cursor is null or cursor size is empty
-        if (c == null || !c.moveToFirst()) {
-            return null;
-        }
-
-        return new String[] {
-                category, // category name
-                c.getString(0), // coverId
-                String.valueOf(c.getCount()), // item count
-                category // category type
-        };
-    }
-
-    @Nullable
-    private Cursor query(@NonNull String[] projection,
-            @Nullable @Category.CategoryType String category, @Nullable String mimeType, int offset,
-            int limit, @NonNull UserId userId) {
-        String selection = IMAGES_VIDEOS_WHERE_CLAUSE;
-        String[] selectionArgs = null;
-
-        if (category != null && Category.getWhereClauseForCategory(category) != null) {
-            selection += " AND (" + Category.getWhereClauseForCategory(category) + ")";
-        }
-
-        if (mimeType != null) {
-            selection += " AND (" + MediaColumns.MIME_TYPE + " LIKE ? )";
-            selectionArgs = new String[] {replaceMatchAnyChar(mimeType)};
-        }
-
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            return queryMedia(limit, mimeType, category, userId);
-        }
-        return queryMediaStore(projection, selection, selectionArgs, offset, limit, userId);
-    }
-
-    @Nullable
-    private Cursor queryMediaStore(@NonNull String[] projection,
-            @Nullable String selection, @Nullable String[] selectionArgs, int offset,
-            int limit, @NonNull UserId userId) {
-
-        if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
-            // If external storage is not ready, we can't load any items from MediaStore.
-            // This shouldn't happen in real world use case. This may happen in tests because test
-            // instrumentation kills the target package before starting the test.
-            Log.w(TAG, "Couldn't query items because external storage is not ready");
-            return null;
-        }
-
-        final Uri contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
-        try (ContentProviderClient client = userId.getContentResolver(mContext)
-                .acquireUnstableContentProviderClient(MediaStore.AUTHORITY)) {
-            if (client == null) {
-                Log.e(TAG, "Unable to acquire unstable content provider for "
-                        + MediaStore.AUTHORITY);
-                return null;
-            }
-            Bundle extras = new Bundle();
-            extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection);
-            extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
-            // DATE_TAKEN is time in milliseconds, whereas DATE_MODIFIED is time in seconds.
-            // Sort by DATE_MODIFIED if DATE_TAKEN is NULL
-            extras.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
-                    "COALESCE(" + MediaColumns.DATE_TAKEN + ","
-                            + MediaColumns.DATE_MODIFIED + "* 1000) DESC, "
-                            + MediaColumns._ID + " DESC");
-            extras.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
-            if (limit != -1) {
-                extras.putInt(ContentResolver.QUERY_ARG_LIMIT, limit);
-            }
-
-            return client.query(contentUri, projection, extras, null);
-        } catch (RemoteException e) {
-            // Do nothing, return null.
-            Log.e(TAG, "RemoteException while querying MediaStore for items with"
-                    + " selection = " + selection
-                    + " selectionArgs = " + Arrays.toString(selectionArgs)
-                    + " limit = " + limit + " offset = " + offset + " userId = " + userId, e);
-            return null;
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Unable to get content resolver for the given userId: " + userId, e);
-            return null;
-        }
+        return queryAlbums(mimeType, userId);
     }
 
     private Cursor queryMedia(int limit, @Nullable String mimeType,
diff --git a/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java b/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
index b7f72dc..9848fbd 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDatabaseHelper.java
@@ -37,7 +37,7 @@
     @VisibleForTesting
     static final String PICKER_DATABASE_NAME = "picker.db";
 
-    private static final int VERSION_T = 5;
+    private static final int VERSION_T = 6;
     private static final int VERSION_LATEST = VERSION_T;
 
     final Context mContext;
@@ -124,6 +124,21 @@
                 + "is_favorite INTEGER,"
                 + "CHECK(local_id IS NOT NULL OR cloud_id IS NOT NULL),"
                 + "UNIQUE(local_id, is_visible))");
+
+        db.execSQL("CREATE TABLE album_media (_id INTEGER PRIMARY KEY AUTOINCREMENT,"
+                + "local_id TEXT,"
+                + "cloud_id TEXT,"
+                + "album_id TEXT,"
+                + "date_taken_ms INTEGER NOT NULL CHECK(date_taken_ms >= 0),"
+                + "sync_generation INTEGER NOT NULL CHECK(sync_generation >= 0),"
+                + "size_bytes INTEGER NOT NULL CHECK(size_bytes > 0),"
+                + "duration_ms INTEGER CHECK(duration_ms >= 0),"
+                + "mime_type TEXT NOT NULL,"
+                + "standard_mime_type_extension INTEGER,"
+                + "CHECK((local_id IS NULL AND cloud_id IS NOT NULL) "
+                + "OR (local_id IS NOT NULL AND cloud_id IS NULL)),"
+                + "UNIQUE(local_id,  album_id),"
+                + "UNIQUE(cloud_id, album_id))");
     }
 
     private static void createLatestIndexes(SQLiteDatabase db) {
@@ -136,6 +151,12 @@
         db.execSQL("CREATE INDEX size_index on media(size_bytes)");
         db.execSQL("CREATE INDEX mime_type_index on media(mime_type)");
         db.execSQL("CREATE INDEX is_favorite_index on media(is_favorite)");
+
+        db.execSQL("CREATE INDEX local_id_album_index on album_media(local_id)");
+        db.execSQL("CREATE INDEX cloud_id_album_index on album_media(cloud_id)");
+        db.execSQL("CREATE INDEX date_taken_album_index on album_media(date_taken_ms)");
+        db.execSQL("CREATE INDEX size_album_index on album_media(size_bytes)");
+        db.execSQL("CREATE INDEX mime_type_album_index on album_media(mime_type)");
     }
 
     private static void clearPickerPrefs(Context context) {
diff --git a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
index 205af85..9cb7d85 100644
--- a/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
+++ b/src/com/android/providers/media/photopicker/data/PickerDbFacade.java
@@ -21,7 +21,6 @@
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorLong;
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
 import static com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar;
-import static com.android.providers.media.util.FileUtils.buildPrimaryVolumeFile;
 import static com.android.providers.media.util.SyntheticPathUtils.getPickerRelativePath;
 
 import android.content.ContentValues;
@@ -36,6 +35,7 @@
 import android.provider.CloudMediaProviderContract;
 import android.provider.MediaStore;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -54,7 +54,6 @@
  * MediaProvider for the Photo Picker.
  */
 public class PickerDbFacade {
-    public static final String PROP_ENABLED = "sys.photopicker.pickerdb.enabled";
     public static final String PROP_DEFAULT_SYNC_DELAY_MS =
             "persist.sys.photopicker.pickerdb.default_sync_delay_ms";
 
@@ -91,6 +90,7 @@
     // external storage path, e.g. /storage/emulated/<userid>. That way FUSE cross-user access is
     // not required for picker paths sent across users
     private static final String PICKER_PATH = "/sdcard/" + getPickerRelativePath();
+    private static final String TABLE_ALBUM_MEDIA = "album_media";
 
     @VisibleForTesting
     public static final String KEY_ID = "_id";
@@ -114,6 +114,8 @@
     public static final String KEY_STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension";
     @VisibleForTesting
     public static final String KEY_IS_FAVORITE = "is_favorite";
+    @VisibleForTesting
+    public static final String KEY_ALBUM_ID = "album_id";
 
     @VisibleForTesting
     public static final String IMAGE_FILE_EXTENSION = ".jpg";
@@ -125,6 +127,7 @@
     private static final String WHERE_CLOUD_ID = KEY_CLOUD_ID + " = ?";
     private static final String WHERE_NULL_CLOUD_ID = KEY_CLOUD_ID + " IS NULL";
     private static final String WHERE_NOT_NULL_CLOUD_ID = KEY_CLOUD_ID + " IS NOT NULL";
+    private static final String WHERE_NOT_NULL_LOCAL_ID = KEY_LOCAL_ID + " IS NOT NULL";
     private static final String WHERE_IS_VISIBLE = KEY_IS_VISIBLE + " = 1";
     private static final String WHERE_MIME_TYPE = KEY_MIME_TYPE + " LIKE ? ";
     private static final String WHERE_IS_FAVORITE = KEY_IS_FAVORITE + " = 1";
@@ -135,6 +138,7 @@
     private static final String WHERE_DATE_TAKEN_MS_BEFORE =
             String.format("%s < ? OR (%s = ? AND %s < ?)",
                     KEY_DATE_TAKEN_MS, KEY_DATE_TAKEN_MS, KEY_ID);
+    private static final String WHERE_ALBUM_ID = KEY_ALBUM_ID  + " = ?";
 
     private static final String[] PROJECTION_ALBUM_CURSOR = new String[] {
         CloudMediaProviderContract.AlbumColumns.ID,
@@ -211,6 +215,14 @@
     }
 
     /**
+     * Returns {@link DbWriteOperation} to add album_media belonging to {@code authority}
+     * into the picker db.
+     */
+    public DbWriteOperation beginAddAlbumMediaOperation(String authority, String albumId) {
+        return new AddAlbumMediaOperation(mDatabase, isLocal(authority), albumId);
+    }
+
+    /**
      * Returns {@link DbWriteOperation} to remove media belonging to {@code authority} from the
      * picker db.
      */
@@ -229,6 +241,16 @@
     }
 
     /**
+     * Returns {@link DbWriteOperation} to clear album media for a given albumId from the picker
+     * db.
+     *
+     * @param authority to determine whether local or cloud media should be cleared
+     */
+    public DbWriteOperation beginResetAlbumMediaOperation(String authority, String albumId) {
+        return new ResetAlbumOperation(mDatabase, isLocal(authority), albumId);
+    }
+
+    /**
      * Represents an atomic write operation to the picker database.
      *
      * <p>This class is not thread-safe and is meant to be used within a single thread only.
@@ -237,12 +259,20 @@
 
         private final SQLiteDatabase mDatabase;
         private final boolean mIsLocal;
+        private final String mAlbumId;
 
         private boolean mIsSuccess = false;
 
+        // Needed for Album Media Write operations.
         private DbWriteOperation(SQLiteDatabase database, boolean isLocal) {
+            this(database, isLocal, "");
+        }
+
+        // Needed for Album Media Write operations.
+        private DbWriteOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
             mDatabase = database;
             mIsLocal = isLocal;
+            mAlbumId = albumId;
             mDatabase.beginTransaction();
         }
 
@@ -289,6 +319,10 @@
             return mIsLocal;
         }
 
+        String albumId() {
+            return mAlbumId;
+        }
+
         int updateMedia(SQLiteQueryBuilder qb, ContentValues values,
                 String[] selectionArgs) {
             try {
@@ -513,16 +547,18 @@
         private final long dateTakenBeforeMs;
         private final long dateTakenAfterMs;
         private final long id;
+        private final String albumId;
         private final long sizeBytes;
         private final String mimeType;
         private final boolean isFavorite;
 
         private QueryFilter(int limit, long dateTakenBeforeMs, long dateTakenAfterMs, long id,
-                long sizeBytes, String mimeType, boolean isFavorite) {
+                String albumId, long sizeBytes, String mimeType, boolean isFavorite) {
             this.limit = limit;
             this.dateTakenBeforeMs = dateTakenBeforeMs;
             this.dateTakenAfterMs = dateTakenAfterMs;
             this.id = id;
+            this.albumId = albumId;
             this.sizeBytes = sizeBytes;
             this.mimeType = mimeType;
             this.isFavorite = isFavorite;
@@ -541,6 +577,7 @@
         private long dateTakenBeforeMs = LONG_DEFAULT;
         private long dateTakenAfterMs = LONG_DEFAULT;
         private long id = LONG_DEFAULT;
+        private String albumId = STRING_DEFAULT;
         private long sizeBytes = LONG_DEFAULT;
         private String mimeType = STRING_DEFAULT;
         private boolean isFavorite = BOOLEAN_DEFAULT;
@@ -575,6 +612,10 @@
             this.id = id;
             return this;
         }
+        public QueryFilterBuilder setAlbumId(String albumId) {
+            this.albumId = albumId;
+            return this;
+        }
 
         public QueryFilterBuilder setSizeBytes(long sizeBytes) {
             this.sizeBytes = sizeBytes;
@@ -597,8 +638,8 @@
         }
 
         public QueryFilter build() {
-            return new QueryFilter(limit, dateTakenBeforeMs, dateTakenAfterMs, id, sizeBytes,
-                    mimeType, isFavorite);
+            return new QueryFilter(limit, dateTakenBeforeMs, dateTakenAfterMs, id, albumId,
+                    sizeBytes, mimeType, isFavorite);
         }
     }
 
@@ -615,7 +656,25 @@
         final SQLiteQueryBuilder qb = createVisibleMediaQueryBuilder();
         final String[] selectionArgs = buildSelectionArgs(qb, query);
 
-        return queryMediaForUi(qb, selectionArgs, query.limit);
+        return queryMediaForUi(qb, selectionArgs, query.limit, TABLE_MEDIA);
+    }
+
+    /**
+     * Returns sorted cloud or local media items from the picker db for a given album (either cloud
+     * or local).
+     *
+     * Returns a {@link Cursor} containing picker db media rows with columns as
+     * {@link CloudMediaProviderContract#MediaColumns} except for is_favorites column because that
+     * column is only used for fetching the Favorites album.
+     *
+     * The result is sorted in reverse chronological order, i.e. newest first, up to a maximum of
+     * {@code limit}. They can also be filtered with {@code query}.
+     */
+    public Cursor queryAlbumMediaForUi(QueryFilter query, boolean isLocal) {
+        final SQLiteQueryBuilder qb = createAlbumMediaQueryBuilder(isLocal);
+        final String[] selectionArgs = buildSelectionArgs(qb, query);
+
+        return queryMediaForUi(qb, selectionArgs, query.limit, TABLE_ALBUM_MEDIA);
     }
 
     /**
@@ -684,10 +743,6 @@
         return c;
     }
 
-    public static boolean isPickerDbEnabled() {
-        return SystemProperties.getBoolean(PROP_ENABLED, true);
-    }
-
     public static int getDefaultPickerDbSyncDelayMs() {
         return SystemProperties.getInt(PROP_DEFAULT_SYNC_DELAY_MS, 1000);
     }
@@ -697,9 +752,9 @@
     }
 
     private Cursor queryMediaForUi(SQLiteQueryBuilder qb, String[] selectionArgs,
-            int limit) {
+            int limit, String tableName) {
         // Use the <table>.<column> form to order _id to avoid ordering against the projection '_id'
-        final String orderBy = "date_taken_ms DESC," + TABLE_MEDIA + "._id DESC";
+        final String orderBy = getOrderClause(tableName);
         final String limitStr = String.valueOf(limit);
 
         // Hold lock while checking the cloud provider and querying so that cursor extras containing
@@ -716,6 +771,10 @@
         }
     }
 
+    private static String getOrderClause(String tableName) {
+        return "date_taken_ms DESC," + tableName + "._id DESC";
+    }
+
     private String[] getCloudMediaProjectionLocked() {
         return new String[] {
             getProjectionAuthorityLocked(),
@@ -819,8 +878,18 @@
     }
 
     private static ContentValues cursorToContentValue(Cursor cursor, boolean isLocal) {
+        return cursorToContentValue(cursor, isLocal, "");
+    }
+
+    private static ContentValues cursorToContentValue(Cursor cursor, boolean isLocal,
+            String albumId) {
         final ContentValues values = new ContentValues();
-        values.put(KEY_IS_VISIBLE, 1);
+        if(TextUtils.isEmpty(albumId)) {
+            values.put(KEY_IS_VISIBLE, 1);
+        }
+        else {
+            values.put(KEY_ALBUM_ID, albumId);
+        }
 
         final int count = cursor.getColumnCount();
         for (int index = 0; index < count; index++) {
@@ -914,10 +983,21 @@
             selectArgs.add(replaceMatchAnyChar(query.mimeType));
         }
 
+        if (query.isFavorite && !TextUtils.isEmpty(query.albumId)) {
+            throw new IllegalStateException(
+                    "If albumId is present, the media cannot be marked as isFavorite as it "
+                            + "represents media for another album.");
+        }
+
         if (query.isFavorite) {
             qb.appendWhereStandalone(WHERE_IS_FAVORITE);
         }
 
+        if(!TextUtils.isEmpty(query.albumId)) {
+            qb.appendWhereStandalone(WHERE_ALBUM_ID);
+            selectArgs.add(query.albumId);
+        }
+
         if (selectArgs.isEmpty()) {
             return null;
         }
@@ -932,6 +1012,19 @@
         return qb;
     }
 
+    private static SQLiteQueryBuilder createAlbumMediaQueryBuilder(boolean isLocal) {
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(TABLE_ALBUM_MEDIA);
+
+        if (isLocal) {
+            qb.appendWhereStandalone(WHERE_NOT_NULL_LOCAL_ID);
+        } else {
+            qb.appendWhereStandalone(WHERE_NOT_NULL_CLOUD_ID);
+        }
+
+        return qb;
+    }
+
     private static SQLiteQueryBuilder createLocalOnlyMediaQueryBuilder() {
         SQLiteQueryBuilder qb = createLocalMediaQueryBuilder();
         qb.appendWhereStandalone(WHERE_NULL_CLOUD_ID);
@@ -973,4 +1066,57 @@
 
         return qb;
     }
+
+
+    private static final class ResetAlbumOperation extends DbWriteOperation {
+
+        private ResetAlbumOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
+            super(database, isLocal, albumId);
+            if(TextUtils.isEmpty(albumId)) {
+                throw new IllegalArgumentException("Missing albumId.");
+            }
+        }
+
+        @Override
+        int executeInternal(@Nullable Cursor unused) {
+            final String albumId = albumId();
+            final boolean isLocal = isLocal();
+
+            final SQLiteQueryBuilder qb = createAlbumMediaQueryBuilder(isLocal);
+            qb.appendWhereStandalone(WHERE_ALBUM_ID);
+            final String[] selectionArgs = new String[]{albumId};
+
+            return qb.delete(getDatabase(), /* selection */ null, /* selectionArgs */
+                    selectionArgs);
+
+        }
+    }
+
+    private static final class AddAlbumMediaOperation extends DbWriteOperation {
+
+        private AddAlbumMediaOperation(SQLiteDatabase database, boolean isLocal, String albumId) {
+            super(database, isLocal, albumId);
+            if(TextUtils.isEmpty(albumId)) {
+                throw new IllegalArgumentException("Missing albumId.");
+            }
+        }
+
+        @Override
+        int executeInternal(@Nullable Cursor cursor) {
+            final boolean isLocal = isLocal();
+            final String albumId = albumId();
+            final SQLiteQueryBuilder qb = createAlbumMediaQueryBuilder(isLocal);
+            int counter = 0;
+
+            while (cursor.moveToNext()) {
+                ContentValues values = cursorToContentValue(cursor, isLocal, albumId);
+                if (qb.insert(getDatabase(), values) > 0) {
+                    counter++;
+                }
+            }
+
+            return counter;
+        }
+    }
+
 }
diff --git a/src/com/android/providers/media/photopicker/data/PickerResult.java b/src/com/android/providers/media/photopicker/data/PickerResult.java
index d2b99d0..55d2ef5 100644
--- a/src/com/android/providers/media/photopicker/data/PickerResult.java
+++ b/src/com/android/providers/media/photopicker/data/PickerResult.java
@@ -17,17 +17,11 @@
 package com.android.providers.media.photopicker.data;
 
 import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Build;
-import android.provider.MediaStore;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
-import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.PickerUriResolver;
 import com.android.providers.media.photopicker.data.model.Item;
 import com.android.providers.media.photopicker.data.model.UserId;
@@ -76,15 +70,10 @@
     }
 
     @VisibleForTesting
-    static Uri getPickerUri(Uri uri, String id) {
+    static Uri getPickerUri(Uri uri) {
         final String userInfo = uri.getUserInfo();
         final String userId = userInfo == null ? UserId.CURRENT_USER.toString() : userInfo;
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            return PickerUriResolver.wrapProviderUri(uri, Integer.parseInt(userId));
-        }
-        final Uri uriWithUserId =
-                PickerUriResolver.PICKER_URI.buildUpon().appendPath(userId).build();
-        return uriWithUserId.buildUpon().appendPath(id).build();
+        return PickerUriResolver.wrapProviderUri(uri, Integer.parseInt(userId));
     }
 
     /**
@@ -96,31 +85,10 @@
     private static List<Uri> getPickerUrisForItems(@NonNull List<Item> ItemList) {
         List<Uri> uris = new ArrayList<>();
         for (Item item : ItemList) {
-            uris.add(getPickerUri(item.getContentUri(), item.getId()));
+            uris.add(getPickerUri(item.getContentUri()));
         }
 
         return uris;
     }
 
-    private static List<Uri> getRedactedUrisForItems(ContentResolver contentResolver,
-            List<Item> ItemList){
-        List<Uri> uris = new ArrayList<>();
-        for (Item item : ItemList) {
-            uris.add(item.getContentUri());
-        }
-
-        if (SdkLevel.isAtLeastS()) {
-            return getRedactedUriFromMediaStoreAPI(contentResolver, uris);
-        } else {
-            // TODO (b/168783994): directly call redacted uri code logic or explore other solution.
-            // This will be addressed in a follow up CL.
-            return uris;
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.S)
-    private static List<Uri> getRedactedUriFromMediaStoreAPI(ContentResolver contentResolver,
-            List<Uri> uris) {
-        return MediaStore.getRedactedUri(contentResolver, uris);
-    }
 }
diff --git a/src/com/android/providers/media/photopicker/data/model/Item.java b/src/com/android/providers/media/photopicker/data/model/Item.java
index def58e2..336e251 100644
--- a/src/com/android/providers/media/photopicker/data/model/Item.java
+++ b/src/com/android/providers/media/photopicker/data/model/Item.java
@@ -24,6 +24,7 @@
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorLong;
 import static com.android.providers.media.photopicker.util.CursorUtils.getCursorString;
 
+import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -33,7 +34,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.providers.media.R;
 import com.android.providers.media.photopicker.data.ItemsProvider;
+import com.android.providers.media.photopicker.util.DateTimeUtils;
 import com.android.providers.media.util.MimeUtils;
 
 /**
@@ -201,14 +204,27 @@
         mGenerationModified = getCursorLong(cursor, ItemColumns.GENERATION_MODIFIED);
         mDuration = getCursorLong(cursor, ItemColumns.DURATION);
         mSpecialFormat = getCursorInt(cursor, ItemColumns.SPECIAL_FORMAT);
-
-        // TODO (b/188867567): Currently, we only has local data source,
-        //  get the uri from provider
         mUri = ItemsProvider.getItemsUri(mId, authority, userId);
 
         parseMimeType();
     }
 
+    public String getContentDescription(@NonNull Context context) {
+        final String itemType;
+        if (isVideo()) {
+            itemType = context.getString(R.string.picker_video);
+        } else if (isGif() || isAnimatedWebp()) {
+            itemType = context.getString(R.string.picker_gif);
+        } else if (isMotionPhoto()) {
+            itemType = context.getString(R.string.picker_motion_photo);
+        } else {
+            itemType = context.getString(R.string.picker_photo);
+        }
+
+        return context.getString(R.string.picker_item_content_desc, itemType,
+                DateTimeUtils.getDateTimeStringForContentDesc(getDateTaken()));
+    }
+
     private void parseMimeType() {
         if (MimeUtils.isImageMimeType(mMimeType)) {
             mIsImage = true;
diff --git a/src/com/android/providers/media/photopicker/ui/DateHeaderHolder.java b/src/com/android/providers/media/photopicker/ui/DateHeaderHolder.java
index b6ba48f..0c6dc4a 100644
--- a/src/com/android/providers/media/photopicker/ui/DateHeaderHolder.java
+++ b/src/com/android/providers/media/photopicker/ui/DateHeaderHolder.java
@@ -41,7 +41,7 @@
         if (dateTaken == 0) {
             mTitle.setText(R.string.recent);
         } else {
-            mTitle.setText(DateTimeUtils.getDateTimeString(dateTaken));
+            mTitle.setText(DateTimeUtils.getDateHeaderString(dateTaken));
         }
     }
 }
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
index 601650f..7804c32 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
@@ -73,6 +73,8 @@
             itemHolder.itemView.setOnClickListener(mOnClickListener);
             itemHolder.itemView.setOnLongClickListener(mOnLongClickListener);
             itemHolder.itemView.setSelected(mSelection.isItemSelected(item));
+            itemHolder.itemView.setContentDescription(
+                    item.getContentDescription(itemHolder.itemView.getContext()));
         }
         itemHolder.bind();
     }
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index e0ced0c..3edc17a 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -54,8 +54,8 @@
 
     private boolean mIsDefaultCategory;
     @CategoryType
-    private String mCategoryType;
-    private String mCategoryName;
+    private String mCategoryType = Category.CATEGORY_DEFAULT;
+    private String mCategoryName = "";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
diff --git a/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java b/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java
index 26f4bd4..a910a00 100644
--- a/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java
+++ b/src/com/android/providers/media/photopicker/ui/PlaybackHandler.java
@@ -40,11 +40,9 @@
     // with lock while reading or writing to it.
     private Uri mVideoUri = null;
     private final ExoPlayerWrapper mExoPlayerWrapper;
-    private final ImageLoader mImageLoader;
 
-    PlaybackHandler(Context context, ImageLoader imageLoader, MuteStatus muteStatus) {
+    PlaybackHandler(Context context, MuteStatus muteStatus) {
         mExoPlayerWrapper = new ExoPlayerWrapper(context, muteStatus);
-        mImageLoader = imageLoader;
     }
 
     /**
@@ -97,22 +95,8 @@
         mExoPlayerWrapper.prepareAndPlay(styledPlayerView, imageView, mVideoUri);
     }
 
-    public void onBind(View itemView) {
-        final Item item = (Item) itemView.getTag();
-        // We set the ImageView with image from the video. ImageView is needed to improve the user
-        // experience while video player is not yet initialized or being prepared.
-        final ImageView imageView = itemView.findViewById(R.id.preview_video_image);
-        mImageLoader.loadImageFromVideoForPreview(item, imageView);
-
-        // Video playback needs granular page state events and hence video playback is initiated by
-        // ViewPagerWrapper and handled by PlaybackHandler#handleVideoPlayback
-    }
-
     public void onViewAttachedToWindow(View itemView) {
-        final ImageView imageView = itemView.findViewById(R.id.preview_video_image);
         final StyledPlayerView styledPlayerView = itemView.findViewById(R.id.preview_player_view);
-
-        imageView.setVisibility(View.VISIBLE);
         styledPlayerView.setVisibility(View.GONE);
         styledPlayerView.setControllerVisibilityListener(null);
         styledPlayerView.hideController();
diff --git a/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java b/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java
index d563e85..785c2da 100644
--- a/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/PreviewAdapter.java
@@ -48,7 +48,7 @@
     PreviewAdapter(Context context, MuteStatus muteStatus) {
         mImageLoader = new ImageLoader(context);
         mRemotePreviewHandler = new RemotePreviewHandler(context);
-        mPlaybackHandler = new PlaybackHandler(context, mImageLoader, muteStatus);
+        mPlaybackHandler = new PlaybackHandler(context, muteStatus);
     }
 
     @NonNull
@@ -57,7 +57,7 @@
         if (viewType == ITEM_TYPE_IMAGE) {
             return new PreviewImageHolder(viewGroup.getContext(), viewGroup, mImageLoader);
         } else {
-            return new PreviewVideoHolder(viewGroup.getContext(), viewGroup,
+            return new PreviewVideoHolder(viewGroup.getContext(), viewGroup, mImageLoader,
                     mIsRemotePreviewEnabled);
         }
     }
@@ -65,12 +65,10 @@
     @Override
     public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
         final Item item = getItem(position);
+        holder.itemView.setContentDescription(
+                item.getContentDescription(holder.itemView.getContext()));
         holder.itemView.setTag(item);
         holder.bind();
-
-        if (item.isVideo() && !mIsRemotePreviewEnabled) {
-            mPlaybackHandler.onBind(holder.itemView);
-        }
     }
 
     @Override
@@ -79,10 +77,15 @@
 
         final Item item = (Item) holder.itemView.getTag();
         if (item.isVideo()) {
+            // TODO(b/222506900): Refactor thumbnail show / hide logic to be handled from a single
+            // place. Currently, we show the thumbnail here and hide it when playback starts in
+            // PlaybackHandler/RemotePreviewHandler.
+            PreviewVideoHolder videoHolder = (PreviewVideoHolder) holder;
+            videoHolder.getImageView().setVisibility(View.VISIBLE);
+
             if (mIsRemotePreviewEnabled) {
-                // TODO(b/216420946): Show thumbnail in preview till remote playback starts.
                 mRemotePreviewHandler.onViewAttachedToWindow(
-                        ((PreviewVideoHolder) holder).getSurfaceView(), item);
+                        videoHolder.getSurfaceView(), videoHolder.getImageView(), item);
                 return;
             }
 
@@ -125,7 +128,6 @@
         }
 
         mPlaybackHandler.releaseResources();
-
     }
 
     void onDestroy() {
diff --git a/src/com/android/providers/media/photopicker/ui/PreviewFragment.java b/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
index d9e074e..fdd4f46 100644
--- a/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
@@ -199,8 +199,7 @@
             // we can always use position=0 as current position.
             updateSelectButtonText(addOrSelectButton,
                     mSelection.isItemSelected(mViewPager2Wrapper.getItemAt(/* position */ 0)));
-            addOrSelectButton.setOnClickListener(
-                    v -> onClickSelect(addOrSelectButton, /* shouldUpdateButtonState */ false));
+            addOrSelectButton.setOnClickListener(v -> onClickSelectButton(addOrSelectButton));
         }
 
         // Set the appropriate special format icon based on the item in the preview
@@ -223,19 +222,19 @@
             ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
         });
 
-        final Button selectButton = view.findViewById(R.id.preview_select_check_button);
-        selectButton.setVisibility(View.VISIBLE);
+        final Button selectedCheckButton = view.findViewById(R.id.preview_selected_check_button);
+        selectedCheckButton.setVisibility(View.VISIBLE);
         // Update the select icon and text according to the state of selection while swiping
         // between photos
-        mViewPager2Wrapper.addOnPageChangeCallback(new OnPageChangeCallback(selectButton));
+        mViewPager2Wrapper.addOnPageChangeCallback(new OnPageChangeCallback(selectedCheckButton));
 
         // Update add button text to include number of items selected.
         mSelection.getSelectedItemCount().observe(this, selectedItemCount -> {
             viewSelectedAddButton.setText(generateAddButtonString(getContext(), selectedItemCount));
         });
 
-        selectButton.setOnClickListener(
-                v -> onClickSelect(selectButton, /* shouldUpdateButtonState */ true));
+        selectedCheckButton.setOnClickListener(
+                v -> onClickSelectedCheckButton(selectedCheckButton));
     }
 
     @Override
@@ -272,7 +271,17 @@
         }
     }
 
-    private void onClickSelect(@NonNull Button selectButton, boolean shouldUpdateButtonState) {
+    private void onClickSelectButton(@NonNull Button selectButton) {
+        final boolean isSelectedNow = updateSelectionAndGetState();
+        updateSelectButtonText(selectButton, isSelectedNow);
+    }
+
+    private void onClickSelectedCheckButton(@NonNull Button selectedCheckButton) {
+        final boolean isSelectedNow = updateSelectionAndGetState();
+        updateSelectedCheckButtonStateAndText(selectedCheckButton, isSelectedNow);
+    }
+
+    private boolean updateSelectionAndGetState() {
         final Item currentItem = mViewPager2Wrapper.getCurrentItem();
         final boolean wasSelectedBefore = mSelection.isItemSelected(currentItem);
 
@@ -290,19 +299,14 @@
         // wasSelectedBefore = false. And item will be added to selected items. Now, user can only
         // deselect the item. Hence, isSelectedNow is opposite of previous state,
         // i.e., isSelectedNow = true.
-        final boolean isSelectedNow = !wasSelectedBefore;
-        if (shouldUpdateButtonState) {
-            updateSelectButtonStateAndText(selectButton, isSelectedNow);
-        } else {
-            updateSelectButtonText(selectButton, isSelectedNow);
-        }
+        return !wasSelectedBefore;
     }
 
     private class OnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
-        private final Button mSelectButton;
+        private final Button mSelectedCheckButton;
 
-        public OnPageChangeCallback(@NonNull Button selectButton) {
-            mSelectButton = selectButton;
+        public OnPageChangeCallback(@NonNull Button selectedCheckButton) {
+            mSelectedCheckButton = selectedCheckButton;
         }
 
         @Override
@@ -313,21 +317,23 @@
             final Item item = mViewPager2Wrapper.getItemAt(position);
             // Set the appropriate select/deselect state for each item in each page based on the
             // selection list.
-            updateSelectButtonStateAndText(mSelectButton, mSelection.isItemSelected(item));
+            updateSelectedCheckButtonStateAndText(mSelectedCheckButton,
+                    mSelection.isItemSelected(item));
 
             // Set the appropriate special format icon based on the item in the preview
             updateSpecialFormatIcon(item);
         }
     }
 
-    private static void updateSelectButtonStateAndText(@NonNull Button selectButton,
+    private static void updateSelectButtonText(@NonNull Button selectButton,
             boolean isSelected) {
-        selectButton.setSelected(isSelected);
-        updateSelectButtonText(selectButton, isSelected);
+        selectButton.setText(isSelected ? R.string.deselect : R.string.select);
     }
 
-    private static void updateSelectButtonText(@NonNull Button selectButton, boolean isSelected) {
-        selectButton.setText(isSelected ? R.string.deselect : R.string.select);
+    private static void updateSelectedCheckButtonStateAndText(@NonNull Button selectedCheckButton,
+            boolean isSelected) {
+        selectedCheckButton.setText(isSelected ? R.string.selected : R.string.deselected);
+        selectedCheckButton.setSelected(isSelected);
     }
 
     private void updateSpecialFormatIcon(Item item) {
diff --git a/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java b/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java
index f724e9f..3d18fe9 100644
--- a/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java
+++ b/src/com/android/providers/media/photopicker/ui/PreviewVideoHolder.java
@@ -19,33 +19,48 @@
 import android.content.Context;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 
 import androidx.viewpager2.widget.ViewPager2;
 
 import com.android.providers.media.R;
+import com.android.providers.media.photopicker.data.model.Item;
 
 /**
  * ViewHolder of a video item within the {@link ViewPager2}
  */
-public class PreviewVideoHolder extends BaseViewHolder {
+class PreviewVideoHolder extends BaseViewHolder {
 
-    private SurfaceView mSurfaceView;
+    private final ImageLoader mImageLoader;
+    private final ImageView mImageView;
+    private final SurfaceView mSurfaceView;
 
-    public PreviewVideoHolder(Context context, ViewGroup parent, boolean enabledCloudMediaPreview) {
+    PreviewVideoHolder(Context context, ViewGroup parent, ImageLoader imageLoader,
+            boolean enabledCloudMediaPreview) {
         super(context, parent, enabledCloudMediaPreview ? R.layout.item_cloud_video_preview
                 : R.layout.item_video_preview);
-        if (enabledCloudMediaPreview) {
-            mSurfaceView = itemView.findViewById(R.id.preview_player_view);
-        }
+
+        mImageView = itemView.findViewById(R.id.preview_video_image);
+        mImageLoader = imageLoader;
+        mSurfaceView = enabledCloudMediaPreview ? itemView.findViewById(R.id.preview_player_view)
+                : null;
     }
 
     @Override
     public void bind() {
         // Video playback needs granular page state events and hence video playback is initiated by
-        // ViewPagerWrapper and handled by PlaybackHandler#handleVideoPlayback
+        // ViewPagerWrapper and handled by PlaybackHandler#handleVideoPlayback.
+        // Here, we set the ImageView with thumbnail from the video, to improve the
+        // user experience while video player is not yet initialized or being prepared.
+        final Item item = (Item) itemView.getTag();
+        mImageLoader.loadImageFromVideoForPreview(item, mImageView);
     }
 
-    public SurfaceView getSurfaceView() {
+    SurfaceView getSurfaceView() {
         return mSurfaceView;
     }
+
+    ImageView getImageView() {
+        return mImageView;
+    }
 }
diff --git a/src/com/android/providers/media/photopicker/ui/TabContainerAdapter.java b/src/com/android/providers/media/photopicker/ui/TabContainerAdapter.java
new file mode 100644
index 0000000..bba9500
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/ui/TabContainerAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.providers.media.photopicker.ui;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+/**
+ * Adapter for {@link TabContainerFragment}'s ViewPager2 to show {@link PhotosTabFragment} and
+ * {@link AlbumsTabFragment}.
+ */
+public class TabContainerAdapter extends FragmentStateAdapter {
+    private final static int TAB_COUNT = 2;
+
+    public TabContainerAdapter(@NonNull Fragment fragment) {
+        super(fragment);
+    }
+
+    @Override
+    public int getItemCount() {
+        return TAB_COUNT;
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int pos) {
+        if (pos == 0) {
+            return new PhotosTabFragment();
+        }
+        return new AlbumsTabFragment();
+    }
+}
diff --git a/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
new file mode 100644
index 0000000..5ec4d65
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/ui/TabContainerFragment.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 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.providers.media.photopicker.ui;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.CompositePageTransformer;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.android.providers.media.R;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+/**
+ * The tab container fragment
+ */
+public class TabContainerFragment extends Fragment {
+    private static final String TAG = "TabContainerFragment";
+    private static final int PHOTOS_TAB_POSITION = 0;
+    private static final int ALBUMS_TAB_POSITION = 1;
+
+    private TabContainerAdapter mTabContainerAdapter;
+    private TabLayoutMediator mTabLayoutMediator;
+    private ViewPager2 mViewPager;
+
+    @Override
+    @NonNull
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+        return inflater.inflate(R.layout.fragment_picker_tab_container, container, false);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mTabContainerAdapter = new TabContainerAdapter(/* fragment */ this);
+        mViewPager = view.findViewById(R.id.picker_tab_viewpager);
+        mViewPager.setAdapter(mTabContainerAdapter);
+
+        // If the ViewPager2 has more than one page with BottomSheetBehavior, the scrolled view
+        // (e.g. RecyclerView) on the second page can't be scrolled. The workaround is to update
+        // nestedScrollingChildRef to the scrolled view on the current page. b/145334244
+        Field fieldNestedScrollingChildRef = null;
+        try {
+            fieldNestedScrollingChildRef = BottomSheetBehavior.class.getDeclaredField(
+                    "nestedScrollingChildRef");
+            fieldNestedScrollingChildRef.setAccessible(true);
+        } catch (NoSuchFieldException ex) {
+            Log.d(TAG, "Can't get the field nestedScrollingChildRef from BottomSheetBehavior", ex);
+        }
+
+        final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(
+                getActivity().findViewById(R.id.bottom_sheet));
+
+        final CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
+        mViewPager.setPageTransformer(compositePageTransformer);
+        compositePageTransformer.addTransformer(new AnimationPageTransformer());
+        compositePageTransformer.addTransformer(
+                new NestedScrollPageTransformer(bottomSheetBehavior, fieldNestedScrollingChildRef));
+
+        // The BottomSheetBehavior looks for the first nested scrolling child to determine how to
+        // handle nested scrolls, it finds the inner recyclerView on ViewPager2 in this case. So, we
+        // need to work around it by setNestedScrollingEnabled false. b/145351873
+        final View firstChild = mViewPager.getChildAt(0);
+        if (firstChild instanceof RecyclerView) {
+            mViewPager.getChildAt(0).setNestedScrollingEnabled(false);
+        }
+
+        final TabLayout tabLayout = getActivity().findViewById(R.id.tab_layout);
+        mTabLayoutMediator = new TabLayoutMediator(tabLayout, mViewPager, (tab, pos) -> {
+            if (pos == PHOTOS_TAB_POSITION) {
+                tab.setText(R.string.picker_photos);
+            } else if (pos == ALBUMS_TAB_POSITION) {
+                tab.setText(R.string.picker_albums);
+            }
+        });
+        mTabLayoutMediator.attach();
+        // TabLayout only supports colorDrawable in xml. And if we set the color in the drawable by
+        // setSelectedTabIndicator method, it doesn't apply the color. So, we set color in xml and
+        // set the drawable for the shape here.
+        tabLayout.setSelectedTabIndicator(R.drawable.picker_tab_indicator);
+    }
+
+    @Override
+    public void onDestroyView() {
+        mTabLayoutMediator.detach();
+        super.onDestroyView();
+    }
+
+    /**
+     * Create the fragment and add it into the FragmentManager
+     *
+     * @param fm the fragment manager
+     */
+    public static void show(FragmentManager fm) {
+        final FragmentTransaction ft = fm.beginTransaction();
+        final TabContainerFragment fragment = new TabContainerFragment();
+        ft.replace(R.id.fragment_container, fragment, TAG);
+        ft.commitAllowingStateLoss();
+    }
+
+    private static class AnimationPageTransformer implements ViewPager2.PageTransformer {
+
+        @Override
+        public void transformPage(@NonNull View view, float pos) {
+            view.setAlpha(1.0f - Math.abs(pos));
+        }
+    }
+
+    private static class NestedScrollPageTransformer implements ViewPager2.PageTransformer {
+        private Field mFieldNestedScrollingChildRef;
+        private BottomSheetBehavior mBottomSheetBehavior;
+
+        public NestedScrollPageTransformer(BottomSheetBehavior bottomSheetBehavior, Field field) {
+            mBottomSheetBehavior = bottomSheetBehavior;
+            mFieldNestedScrollingChildRef = field;
+        }
+
+        @Override
+        public void transformPage(@NonNull View view, float pos) {
+            // If pos != 0, it is not in current page, don't update the nested scrolling child
+            // reference.
+            if (pos != 0 || mFieldNestedScrollingChildRef == null) {
+                return;
+            }
+
+            try {
+                final View childView = view.findViewById(R.id.picker_tab_recyclerview);
+                if (childView != null) {
+                    mFieldNestedScrollingChildRef.set(mBottomSheetBehavior,
+                            new WeakReference(childView));
+                }
+            } catch (IllegalAccessException ex) {
+                Log.d(TAG, "Set nestedScrollingChildRef to BottomSheetBehavior fail", ex);
+            }
+        }
+    }
+}
diff --git a/src/com/android/providers/media/photopicker/ui/TabFragment.java b/src/com/android/providers/media/photopicker/ui/TabFragment.java
index ca97f44..1363761 100644
--- a/src/com/android/providers/media/photopicker/ui/TabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabFragment.java
@@ -40,7 +40,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.fragment.app.Fragment;
 import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -106,10 +105,13 @@
         mEmptyView = view.findViewById(android.R.id.empty);
         mEmptyTextView = mEmptyView.findViewById(R.id.empty_text_view);
 
-        mButtonDisabledIconAndTextColor = getContext().getColor(
-                R.color.picker_profile_disabled_button_content_color);
-        mButtonDisabledBackgroundColor = getContext().getColor(
-                R.color.picker_profile_disabled_button_background_color);
+        final int[] attrsDisabled =
+                new int[]{R.attr.pickerDisabledProfileButtonColor,
+                        R.attr.pickerDisabledProfileButtonTextColor};
+        final TypedArray taDisabled = getContext().obtainStyledAttributes(attrsDisabled);
+        mButtonDisabledBackgroundColor = taDisabled.getColor(/* index */ 0, /* defValue */ -1);
+        mButtonDisabledIconAndTextColor = taDisabled.getColor(/* index */ 1, /* defValue */ -1);
+        taDisabled.recycle();
 
         final int[] attrs =
                 new int[]{R.attr.pickerProfileButtonColor, R.attr.pickerProfileButtonTextColor};
@@ -118,17 +120,17 @@
         mButtonIconAndTextColor = ta.getColor(/* index */ 1, /* defValue */ -1);
         ta.recycle();
 
-        mProfileButton = view.findViewById(R.id.profile_button);
+        mProfileButton = getActivity().findViewById(R.id.profile_button);
         mUserIdManager = mPickerViewModel.getUserIdManager();
 
         final boolean canSelectMultiple = mSelection.canSelectMultiple();
         if (canSelectMultiple) {
-            final Button addButton = view.findViewById(R.id.button_add);
+            final Button addButton = getActivity().findViewById(R.id.button_add);
             addButton.setOnClickListener(v -> {
                 ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
             });
 
-            final Button viewSelectedButton = view.findViewById(R.id.button_view_selected);
+            final Button viewSelectedButton = getActivity().findViewById(R.id.button_view_selected);
             // Transition to PreviewFragment on clicking "View Selected".
             viewSelectedButton.setOnClickListener(v -> {
                 mSelection.prepareSelectedItemsForPreviewAll();
@@ -138,7 +140,7 @@
             mBottomBarSize = (int) getResources().getDimension(R.dimen.picker_bottom_bar_size);
 
             mSelection.getSelectedItemCount().observe(this, selectedItemListSize -> {
-                final View bottomBar = view.findViewById(R.id.picker_bottom_bar);
+                final View bottomBar = getActivity().findViewById(R.id.picker_bottom_bar);
                 int dimen = 0;
                 if (selectedItemListSize == 0) {
                     bottomBar.setVisibility(View.GONE);
diff --git a/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewHandler.java b/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewHandler.java
index 1a4010f..22cd0d2 100644
--- a/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewHandler.java
+++ b/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewHandler.java
@@ -20,24 +20,27 @@
 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER;
 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_EVENT_CALLBACK;
 import static android.provider.CloudMediaProviderContract.METHOD_CREATE_SURFACE_CONTROLLER;
-import static android.provider.CloudMediaProvider.SurfaceEventCallback.PLAYBACK_EVENT_READY;
 
 import static com.android.providers.media.PickerUriResolver.createSurfaceControllerUri;
 
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PlaybackEvent;
 import android.provider.ICloudMediaSurfaceController;
-
 import android.provider.ICloudSurfaceEventCallback;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.View;
+import android.widget.ImageView;
 
 import com.android.providers.media.photopicker.data.model.Item;
 
@@ -45,7 +48,7 @@
 
 /**
  * Manages playback of videos on a {@link Surface} with a
- * {@link android.provider.CloudMediaProvider.SurfaceController} populated remotely.
+ * {@link android.provider.CloudMediaProvider.CloudMediaSurfaceController} populated remotely.
  *
  * <p>This class is not thread-safe and the methods are meant to be always called on the main
  * thread.
@@ -60,9 +63,11 @@
     private final Map<String, SurfaceControllerProxy> mControllers =
             new ArrayMap<>();
     private final SurfaceHolder.Callback mSurfaceHolderCallback = new PreviewSurfaceCallback();
-    private final SurfaceEventCallbackWrapper mSurfaceEventCallbackWrapper;
+    private final SurfaceEventCallbackWrapper mSurfaceEventCallbackWrapper =
+            new SurfaceEventCallbackWrapper();
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+    private final ItemPreviewState mCurrentPreviewState = new ItemPreviewState();
 
-    private Item mCurrentPreviewItem;
     private boolean mIsInBackground = false;
     private int mSurfaceCounter = 0;
 
@@ -72,30 +77,32 @@
 
     public RemotePreviewHandler(Context context) {
         mContext = context;
-        mSurfaceEventCallbackWrapper = new SurfaceEventCallbackWrapper();
     }
 
     /**
      * Prepares the given {@link SurfaceView} for remote preview of the given {@link Item}.
      *
-     * @param view {@link SurfaceView} for preview of the media item
-     * @param item {@link Item} to be previewed
+     * @param surfaceView {@link SurfaceView} for preview of the media item
+     * @param imageView   {@link ImageView} for thumbnail of the media item
+     * @param item        {@link Item} to be previewed
      * @return true if the given {@link Item} can be previewed remotely, else false
      */
-    public boolean onViewAttachedToWindow(SurfaceView view, Item item) {
-        RemotePreviewSession session = createRemotePreviewSession(item);
+    public boolean onViewAttachedToWindow(SurfaceView surfaceView, ImageView imageView, Item item) {
+        RemotePreviewSession session = createRemotePreviewSession(item, imageView);
         if (session == null) {
             Log.w(TAG, "Failed to create RemotePreviewSession.");
             return false;
         }
 
-        SurfaceHolder holder = view.getHolder();
+        SurfaceHolder holder = surfaceView.getHolder();
         mSessionMap.put(holder, session);
         // Ensure that we don't add the same callback twice, since we don't remove callbacks
         // anywhere else.
         holder.removeCallback(mSurfaceHolderCallback);
         holder.addCallback(mSurfaceHolderCallback);
-        mCurrentPreviewItem = item;
+
+        mCurrentPreviewState.item = item;
+        mCurrentPreviewState.thumbnailView = imageView;
 
         return true;
     }
@@ -116,7 +123,7 @@
             return false;
         }
 
-        session.playMedia();
+        session.requestPlayMedia();
         return true;
     }
 
@@ -137,26 +144,28 @@
         destroyAllSurfaceControllers();
     }
 
-    private RemotePreviewSession createRemotePreviewSession(Item item) {
+    private RemotePreviewSession createRemotePreviewSession(Item item, ImageView imageView) {
         String authority = item.getContentUri().getAuthority();
         SurfaceControllerProxy controller = getSurfaceController(authority);
         if (controller == null) {
             return null;
         }
 
-        return new RemotePreviewSession(mSurfaceCounter++, item.getId(), authority, controller);
+        return new RemotePreviewSession(mSurfaceCounter++, item.getId(), authority, controller,
+                imageView);
     }
 
-    private void restorePreviewState(SurfaceHolder holder, Item item) {
-        RemotePreviewSession session = createRemotePreviewSession(item);
+    private void restorePreviewState(SurfaceHolder holder) {
+        mCurrentPreviewState.thumbnailView.setVisibility(View.VISIBLE);
+        RemotePreviewSession session = createRemotePreviewSession(mCurrentPreviewState.item,
+                mCurrentPreviewState.thumbnailView);
         if (session == null) {
             throw new IllegalStateException("Failed to restore preview state.");
         }
 
         mSessionMap.put(holder, session);
         session.surfaceCreated(holder.getSurface());
-        // TODO(b/215175249): Start playback when player is ready.
-        session.playMedia();
+        session.requestPlayMedia();
     }
 
     private RemotePreviewSession getSessionForItem(Item item) {
@@ -228,23 +237,19 @@
     private final class SurfaceEventCallbackWrapper extends ICloudSurfaceEventCallback.Stub {
 
         @Override
-        public void onPlaybackEvent(int surfaceId, int eventType, Bundle eventInfo) {
-            final RemotePreviewSession session = getSessionForSurfaceId(surfaceId);
+        public void onPlaybackEvent(int surfaceId, @PlaybackEvent int eventType,
+                @Nullable Bundle eventInfo) {
+            Log.d(TAG, "Received onPlaybackEvent for surfaceId: " + surfaceId +
+                    " ; eventType: " + eventType + " ; eventInfo: " + eventInfo);
 
-            if (session == null) {
-                Log.w(TAG, "No RemotePreviewSession found.");
-                return;
-            }
-            switch (eventType) {
-                case PLAYBACK_EVENT_READY:
-                    session.playMedia();
+            mMainThreadHandler.post(() -> {
+                final RemotePreviewSession session = getSessionForSurfaceId(surfaceId);
+                if (session == null) {
+                    Log.w(TAG, "No RemotePreviewSession found.");
                     return;
-                default:
-                    Log.d(TAG, "RemotePreviewHandler onPlaybackEvent for surfaceId: " +
-                            surfaceId + " ; media id: " + session.getMediaId() +
-                            " ; eventType: " + eventType + " ; eventInfo: " + eventInfo);
-
-            }
+                }
+                session.onPlaybackEvent(eventType, eventInfo);
+            });
         }
     }
 
@@ -254,10 +259,10 @@
         public void surfaceCreated(SurfaceHolder holder) {
             Log.i(TAG, "Surface created: " + holder);
 
-            if (mIsInBackground && mCurrentPreviewItem != null) {
+            if (mIsInBackground) {
                 // This indicates that the app has just come to foreground, and we need to
                 // restore the preview state.
-                restorePreviewState(holder, mCurrentPreviewItem);
+                restorePreviewState(holder);
                 mIsInBackground = false;
                 return;
             }
@@ -285,4 +290,9 @@
             mSessionMap.remove(holder);
         }
     }
+
+    private static final class ItemPreviewState {
+        Item item;
+        ImageView thumbnailView;
+    }
 }
diff --git a/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewSession.java b/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewSession.java
index 35b421b..3dff763 100644
--- a/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewSession.java
+++ b/src/com/android/providers/media/photopicker/ui/remotepreview/RemotePreviewSession.java
@@ -16,10 +16,20 @@
 
 package com.android.providers.media.photopicker.ui.remotepreview;
 
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_ERROR_PERMANENT_FAILURE;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_ERROR_RETRIABLE_FAILURE;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_PAUSED;
+import static android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PLAYBACK_EVENT_READY;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
 import android.os.RemoteException;
+import android.provider.CloudMediaProvider.CloudMediaSurfaceEventCallback.PlaybackEvent;
 import android.util.Log;
 import android.view.Surface;
+import android.view.View;
+import android.widget.ImageView;
 
 /**
  * Handles preview of a given media on a {@link Surface}.
@@ -32,16 +42,20 @@
     private final String mMediaId;
     private final String mAuthority;
     private final SurfaceControllerProxy mSurfaceController;
+    private final ImageView mThumbnailView;
 
     private boolean mIsSurfaceCreated = false;
     private boolean mIsPlaying = false;
+    private boolean mIsPlaybackRequested = false;
+    private boolean mIsPlayerReady = false;
 
     RemotePreviewSession(int surfaceId, @NonNull String mediaId, @NonNull String authority,
-            @NonNull SurfaceControllerProxy surfaceController) {
+            @NonNull SurfaceControllerProxy surfaceController, @NonNull ImageView thumbnailView) {
         this.mSurfaceId = surfaceId;
         this.mMediaId = mediaId;
         this.mAuthority = authority;
         this.mSurfaceController = surfaceController;
+        this.mThumbnailView = thumbnailView;
     }
 
     int getSurfaceId() {
@@ -77,8 +91,7 @@
 
     void surfaceDestroyed() {
         if (!mIsSurfaceCreated) {
-            Log.w(TAG, "Surface is not created.");
-            return;
+            throw new IllegalStateException("Surface is not created.");
         }
 
         try {
@@ -91,8 +104,7 @@
 
     void surfaceChanged(int format, int width, int height) {
         if (!mIsSurfaceCreated) {
-            Log.w(TAG, "Surface is not created.");
-            return;
+            throw new IllegalStateException("Surface is not created.");
         }
 
         try {
@@ -102,23 +114,57 @@
         }
     }
 
-    void playMedia() {
+    void requestPlayMedia() {
         // When the user is at the first item in ViewPager, swiping further right trigger the
         // callback {@link ViewPager2.PageTransformer#transforPage(View, int)}, which would call
-        // into playMedia again. Hence we want to check is its already playing, before making the
-        // call to {@link SurfaceControllerProxy}.
+        // into requestPlayMedia again. Hence, we want to check is its already playing, before
+        // proceeding further.
         if (mIsPlaying) {
             return;
         }
 
-        if (!mIsSurfaceCreated) {
-            Log.w(TAG, "Surface is not created.");
+        if (mIsPlayerReady) {
+            playMedia();
             return;
         }
 
+        mIsPlaybackRequested = true;
+    }
+
+    void onPlaybackEvent(@PlaybackEvent int eventType, @Nullable Bundle eventInfo) {
+        switch (eventType) {
+            case PLAYBACK_EVENT_READY:
+                mIsPlayerReady = true;
+
+                if (mIsPlaybackRequested) {
+                    playMedia();
+                    mIsPlaybackRequested = false;
+                }
+                return;
+            case PLAYBACK_EVENT_ERROR_PERMANENT_FAILURE:
+            case PLAYBACK_EVENT_ERROR_RETRIABLE_FAILURE:
+                mIsPlayerReady = false;
+                return;
+            case PLAYBACK_EVENT_PAUSED:
+                mIsPlaying = false;
+                return;
+            default:
+        }
+    }
+
+    private void playMedia() {
+        if (!mIsSurfaceCreated) {
+            throw new IllegalStateException("Surface is not created.");
+        }
+        if (mIsPlaying) {
+            throw new IllegalStateException("Player is already playing.");
+        }
+
+        mThumbnailView.setVisibility(View.GONE);
+
         try {
             mSurfaceController.onMediaPlay(mSurfaceId);
-            mIsPlaying = false;
+            mIsPlaying = true;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to play media.", e);
         }
diff --git a/src/com/android/providers/media/photopicker/util/DateTimeUtils.java b/src/com/android/providers/media/photopicker/util/DateTimeUtils.java
index 234067c..263256f 100644
--- a/src/com/android/providers/media/photopicker/util/DateTimeUtils.java
+++ b/src/com/android/providers/media/photopicker/util/DateTimeUtils.java
@@ -19,7 +19,6 @@
 import static android.icu.text.DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
 import static android.icu.text.RelativeDateTimeFormatter.Style.LONG;
 
-import android.content.Context;
 import android.icu.text.DateFormat;
 import android.icu.text.DisplayContext;
 import android.icu.text.RelativeDateTimeFormatter;
@@ -43,11 +42,12 @@
  */
 public class DateTimeUtils {
 
-    private static final String DATE_FORMAT_SKELETON = "EMMMd";
     private static final String DATE_FORMAT_SKELETON_WITH_YEAR = "EMMMdy";
+    private static final String DATE_FORMAT_SKELETON_WITHOUT_YEAR = "EMMMd";
+    private static final String DATE_FORMAT_SKELETON_WITH_TIME = "MMMdyhmmss";
 
     /**
-     * Formats a time according to the local conventions.
+     * Formats a time according to the local conventions for PhotoGrid.
      *
      * If the difference of the date between the time and now is zero, show
      * "Today".
@@ -57,21 +57,34 @@
      * If they have different years, show the weekday, the date and the year.
      * E.g. "Sat, Jun 5, 2021"
      *
-     * @param context the context
      * @param when    the time to be formatted. The unit is in milliseconds
      *                since January 1, 1970 00:00:00.0 UTC.
      * @return the formatted string
      */
-    public static String getDateTimeString(long when) {
+    public static String getDateHeaderString(long when) {
         // Get the system time zone
         final ZoneId zoneId = ZoneId.systemDefault();
         final LocalDate nowDate = LocalDate.now(zoneId);
 
-        return getDateTimeString(when, nowDate);
+        return getDateHeaderString(when, nowDate);
+    }
+
+    /**
+     * Formats a time according to the local conventions for content description.
+     *
+     * The format of the returned string is fixed to {@code DATE_FORMAT_SKELETON_WITH_TIME}.
+     * E.g. "Feb 2, 2022, 2:22:22 PM"
+     *
+     * @param when    the time to be formatted. The unit is in milliseconds
+     *                since January 1, 1970 00:00:00.0 UTC.
+     * @return the formatted string
+     */
+    public static String getDateTimeStringForContentDesc(long when) {
+        return getDateTimeString(when, DATE_FORMAT_SKELETON_WITH_TIME, Locale.getDefault());
     }
 
     @VisibleForTesting
-    static String getDateTimeString(long when, LocalDate nowDate) {
+    static String getDateHeaderString(long when, LocalDate nowDate) {
         // Get the system time zone
         final ZoneId zoneId = ZoneId.systemDefault();
         final LocalDate whenDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(when),
@@ -87,7 +100,7 @@
         } else {
             final String skeleton;
             if (whenDate.getYear() == nowDate.getYear()) {
-                skeleton = DATE_FORMAT_SKELETON;
+                skeleton = DATE_FORMAT_SKELETON_WITHOUT_YEAR;
             } else {
                 skeleton = DATE_FORMAT_SKELETON_WITH_YEAR;
             }
diff --git a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
index 3591c45..0f1f202 100644
--- a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
+++ b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
@@ -28,6 +28,8 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.provider.CloudMediaProvider;
+import android.text.TextUtils;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -41,6 +43,7 @@
  */
 public class PickerProviderMediaGenerator {
     private static final Map<String, MediaGenerator> sMediaGeneratorMap = new HashMap<>();
+    private static final String TAG = "PickerProviderMediaGenerator";
     private static final String[] MEDIA_PROJECTION = new String[] {
         MediaColumns.ID,
         MediaColumns.MEDIA_STORE_URI,
@@ -52,6 +55,16 @@
         MediaColumns.DURATION_MILLIS,
         MediaColumns.IS_FAVORITE,
     };
+    private static final String[] ALBUM_MEDIA_PROJECTION = new String[] {
+            MediaColumns.ID,
+            MediaColumns.MEDIA_STORE_URI,
+            MediaColumns.MIME_TYPE,
+            MediaColumns.STANDARD_MIME_TYPE_EXTENSION,
+            MediaColumns.DATE_TAKEN_MILLIS,
+            MediaColumns.SYNC_GENERATION,
+            MediaColumns.SIZE_BYTES,
+            MediaColumns.DURATION_MILLIS,
+    };
 
     private static final String[] ALBUM_PROJECTION = new String[] {
         AlbumColumns.ID,
@@ -81,8 +94,8 @@
         private Intent mAccountConfigurationIntent;
 
         // TODO(b/214592293): Add pagination support for testing purposes.
-        public Cursor getMedia(long generation, String albumdId, String mimeType, long sizeBytes) {
-            return getCursor(mMedia, generation, albumdId, mimeType, sizeBytes,
+        public Cursor getMedia(long generation, String albumId, String mimeType, long sizeBytes) {
+            return getCursor(mMedia, generation, albumId, mimeType, sizeBytes,
                     /* isDeleted */ false);
         }
 
@@ -118,6 +131,10 @@
             mMedia.add(0, createTestMedia(localId, cloudId));
         }
 
+        public void addAlbumMedia(String localId, String cloudId, String albumId) {
+            mMedia.add(0, createTestAlbumMedia(localId, cloudId, albumId));
+        }
+
         public void addMedia(String localId, String cloudId, String albumId, String mimeType,
                 int standardMimeTypeExtension, long sizeBytes, boolean isFavorite) {
             mDeletedMedia.remove(createPlaceholderMedia(localId, cloudId));
@@ -158,6 +175,10 @@
             // Increase generation
             return new TestMedia(localId, cloudId, ++mLastSyncGeneration);
         }
+        private TestMedia createTestAlbumMedia(String localId, String cloudId, String albumId) {
+            // Increase generation
+            return new TestMedia(localId, cloudId, albumId);
+        }
 
         private TestMedia createTestMedia(String localId, String cloudId, String albumId,
                 String mimeType, int standardMimeTypeExtension, long sizeBytes,
@@ -178,12 +199,17 @@
             final MatrixCursor matrix;
             if (isDeleted) {
                 matrix = new MatrixCursor(DELETED_MEDIA_PROJECTION);
+            } else if(!TextUtils.isEmpty(albumId)) {
+                matrix = new MatrixCursor(ALBUM_MEDIA_PROJECTION);
             } else {
                 matrix = new MatrixCursor(MEDIA_PROJECTION);
             }
 
             for (TestMedia media : mediaList) {
-                if (media.generation > generation
+                if (!TextUtils.isEmpty(albumId) && matchesFilter(media,
+                        albumId, mimeType, sizeBytes)) {
+                    matrix.addRow(media.toAlbumMediaArray());
+                } else if (media.generation > generation
                         && matchesFilter(media, albumId, mimeType, sizeBytes)) {
                     matrix.addRow(media.toArray(isDeleted));
                 }
@@ -224,6 +250,14 @@
                     /* isFavorite */ false);
         }
 
+
+        public TestMedia(String localId, String cloudId, String albumId) {
+            this(localId, cloudId, /* albumId */ albumId, "image/jpeg",
+                    /* standardMimeTypeExtension */ MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE,
+                    /* sizeBytes */ 4096, /* durationMs */ 0, 0,
+                    /* isFavorite */ false);
+        }
+
         public TestMedia(String localId, String cloudId, String albumId, String mimeType,
                 int standardMimeTypeExtension, long sizeBytes, long durationMs, long generation,
                 boolean isFavorite) {
@@ -258,6 +292,19 @@
             };
         }
 
+        public String[] toAlbumMediaArray() {
+            return new String[] {
+                    getId(),
+                    localId == null ? null : "content://media/external/files/" + localId,
+                    mimeType,
+                    String.valueOf(standardMimeTypeExtension),
+                    String.valueOf(dateTakenMs),
+                    String.valueOf(generation),
+                    String.valueOf(sizeBytes),
+                    String.valueOf(durationMs)
+            };
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o == null || !(o instanceof TestMedia)) {
diff --git a/tests/src/com/android/providers/media/PickerUriResolverTest.java b/tests/src/com/android/providers/media/PickerUriResolverTest.java
index e0abb1a..672ad18 100644
--- a/tests/src/com/android/providers/media/PickerUriResolverTest.java
+++ b/tests/src/com/android/providers/media/PickerUriResolverTest.java
@@ -19,7 +19,6 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.provider.MediaStore.MediaColumns._ID;
-import static android.provider.MediaStore.MediaColumns.RELATIVE_PATH;
 
 import static androidx.test.InstrumentationRegistry.getTargetContext;
 
@@ -31,7 +30,6 @@
 import static org.junit.Assert.fail;
 
 import android.Manifest;
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
@@ -51,7 +49,6 @@
 
 import com.android.providers.media.photopicker.PickerSyncController;
 import com.android.providers.media.photopicker.data.PickerDbFacade;
-import com.android.providers.media.photopicker.data.model.UserId;
 import com.android.providers.media.scan.MediaScannerTest;
 
 import org.junit.AfterClass;
@@ -83,14 +80,6 @@
         }
 
         @Override
-        protected Uri getRedactedUri(ContentResolver contentResolver, Uri uri) {
-            // Cannot mock static method MediaStore.getRedactedUri(). Cannot mock implementation of
-            // MediaStore.getRedactedUri as it depends on final methods which cannot be mocked as
-            // well.
-            return uri;
-        }
-
-        @Override
         Cursor queryPickerUri(Uri uri, String[] projection) {
             if (!uri.getLastPathSegment().equals(TEST_ID)) {
                 return super.queryPickerUri(uri, projection);
@@ -352,15 +341,12 @@
     }
 
     private static Uri getPickerUriForId(long id, int user) {
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            final Uri providerUri = PickerUriResolver
-                    .getMediaUri(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)
-                    .buildUpon()
-                    .appendPath(String.valueOf(id))
-                    .build();
-            return PickerUriResolver.wrapProviderUri(providerUri, user);
-        }
-        return Uri.parse("content://media/picker/" + user + "/" + id);
+        final Uri providerUri = PickerUriResolver
+                .getMediaUri(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)
+                .buildUpon()
+                .appendPath(String.valueOf(id))
+                .build();
+        return PickerUriResolver.wrapProviderUri(providerUri, user);
     }
 
     private void testOpenFile(Uri uri) throws Exception {
@@ -379,8 +365,7 @@
 
     private void testQuery(Uri uri) throws Exception {
         Cursor result = sTestPickerUriResolver.query(uri,
-                /* projection */ new String[]{_ID},
-                /* queryArgs */ null, /* signal */ null, /* callingPid */ -1, /* callingUid */ -1);
+                /* projection */ new String[]{_ID}, /* callingPid */ -1, /* callingUid */ -1);
         assertThat(result).isNotNull();
         assertThat(result.getCount()).isEqualTo(1);
         result.moveToFirst();
@@ -416,7 +401,7 @@
 
     private void testQueryInvalidUser(Uri uri) throws Exception {
         Cursor result = sTestPickerUriResolver.query(uri, /* projection */ null,
-                /* queryArgs */ null, /* signal */ null, /* callingPid */ -1, /* callingUid */ -1);
+                /* callingPid */ -1, /* callingUid */ -1);
         assertThat(result).isNotNull();
         assertThat(result.getCount()).isEqualTo(0);
     }
@@ -460,8 +445,8 @@
 
     private void testQuery_permissionDenied(Uri uri) throws Exception {
         try {
-            sTestPickerUriResolver.query(uri, /* projection */ null, /* queryArgs */ null,
-                    /* signal */ null, /* callingPid */ -1, /* callingUid */ -1);
+            sTestPickerUriResolver.query(uri, /* projection */ null
+                    , /* callingPid */ -1, /* callingUid */ -1);
             fail("query should fail if the caller does not have permission grant on"
                     + " the picker uri: " + uri);
         } catch (SecurityException expected) {
diff --git a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
index ac5a557..ddacc9c 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
@@ -48,7 +48,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,11 +89,8 @@
     private static final Pair<String, String> LOCAL_ONLY_2 = Pair.create(LOCAL_ID_2, null);
     private static final Pair<String, String> CLOUD_ONLY_1 = Pair.create(null, CLOUD_ID_1);
     private static final Pair<String, String> CLOUD_ONLY_2 = Pair.create(null, CLOUD_ID_2);
-    private static final Pair<String, String> CLOUD_AND_LOCAL_1
-            = Pair.create(LOCAL_ID_1, CLOUD_ID_1);
 
     private static final String COLLECTION_1 = "1";
-    private static final String COLLECTION_2 = "2";
 
     private static final String IMAGE_MIME_TYPE = "image/jpeg";
     private static final String VIDEO_MIME_TYPE = "video/mp4";
@@ -130,8 +126,6 @@
 
         // Set cloud provider to null to discard
         mFacade.setCloudProvider(null);
-
-        Assume.assumeTrue(PickerDbFacade.isPickerDbEnabled());
     }
 
     @Test
diff --git a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
index b60dfb9..a20d8fa 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
@@ -19,8 +19,6 @@
 import static com.android.providers.media.PickerProviderMediaGenerator.ALBUM_COLUMN_TYPE_CLOUD;
 import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
 import static com.android.providers.media.photopicker.PickerSyncController.CloudProviderInfo;
-import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT;
-import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT;
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -46,7 +44,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -78,9 +75,6 @@
     private static final String ALBUM_ID_1 = "1";
     private static final String ALBUM_ID_2 = "2";
 
-    private static final String MIME_TYPE_DEFAULT = STRING_DEFAULT;
-    private static final long SIZE_BYTES_DEFAULT = LONG_DEFAULT;
-
     private static final Pair<String, String> LOCAL_ONLY_1 = Pair.create(LOCAL_ID_1, null);
     private static final Pair<String, String> LOCAL_ONLY_2 = Pair.create(LOCAL_ID_2, null);
     private static final Pair<String, String> CLOUD_ONLY_1 = Pair.create(null, CLOUD_ID_1);
@@ -91,10 +85,6 @@
     private static final String COLLECTION_1 = "1";
     private static final String COLLECTION_2 = "2";
 
-    private static final String IMAGE_MIME_TYPE = "image/jpeg";
-    private static final String VIDEO_MIME_TYPE = "video/mp4";
-    private static final long SIZE_BYTES = 50;
-
     private static final long SYNC_DELAY_MS = 1000;
 
     private static final int DB_VERSION_1 = 1;
@@ -130,15 +120,13 @@
         // Set cloud provider to null to avoid trying to sync it during other tests
         // that might be using an IsolatedContext
         mController.setCloudProvider(null);
-
-        Assume.assumeTrue(PickerDbFacade.isPickerDbEnabled());
     }
 
     @Test
     public void testSyncAllMediaLocalOnly() {
         // 1. Do nothing
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 2. Add local only media
         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
@@ -176,9 +164,65 @@
         mLocalMediaGenerator.setMediaCollectionId(COLLECTION_2);
         mController.syncAllMedia();
 
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
     }
 
+    @Test
+    public void testSyncAllAlbumMediaLocalOnly() {
+        // 1. Do nothing
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromMediaQuery();
+
+        // 2. Add local only media
+        addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_1.first, LOCAL_ONLY_1.second, ALBUM_ID_1);
+        addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_2.first, LOCAL_ONLY_2.second, ALBUM_ID_1);
+
+        mController.syncAlbumMedia(ALBUM_ID_1);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, true)) {
+            assertThat(cr.getCount()).isEqualTo(2);
+
+            assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
+            assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 3. Syncs only given album's media
+        addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_1.first, LOCAL_ONLY_1.second, ALBUM_ID_2);
+
+        mController.syncAlbumMedia(ALBUM_ID_1);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, true)) {
+            assertThat(cr.getCount()).isEqualTo(2);
+
+            assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
+            assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 4. Syncing and querying another Album, gets you only items from that album
+        mController.syncAlbumMedia(ALBUM_ID_2);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_2, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 5. Reset media without version bump, still resets as we always do a full sync for albums.
+        mLocalMediaGenerator.resetAll();
+        mController.syncAlbumMedia(ALBUM_ID_1);
+
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, true);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_2, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 6. Sync another album after reset and check that is empty too.
+        mController.syncAlbumMedia(ALBUM_ID_2);
+
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_2, true);
+    }
 
     @Test
     public void testSyncAllMediaCloudOnly() {
@@ -186,12 +230,12 @@
         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2);
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 2. Set secondary cloud provider
         mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 3. Set primary cloud provider
         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -207,7 +251,7 @@
         // 4. Set secondary cloud provider again
         mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 5. Set primary cloud provider once again
         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -222,7 +266,119 @@
         // 6. Clear cloud provider
         mController.setCloudProvider(/* authority */ null);
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
+    }
+
+    @Test
+    public void testSyncAllAlbumMediaCloudOnly() {
+        // 1. Add media before setting primary cloud provider
+        addAlbumMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1.first, CLOUD_ONLY_1.second,
+                ALBUM_ID_1);
+        addAlbumMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2.first, CLOUD_ONLY_2.second,
+                ALBUM_ID_1);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 2. Set secondary cloud provider
+        mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 3. Set primary cloud provider
+        mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(2);
+
+            assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+        // 4. Set secondary cloud provider again
+        mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 5. Set primary cloud provider once again
+        mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(2);
+
+            assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+        // 6. Clear cloud provider
+        mController.setCloudProvider(/* authority */ null);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+    }
+
+    @Test
+    public void testSyncAllAlbumMediaCloudAndLocal() {
+        // 1. Add media before setting primary cloud provider
+        addAlbumMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1.first, CLOUD_ONLY_1.second,
+                ALBUM_ID_1);
+        addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_1.first, LOCAL_ONLY_1.second,
+                ALBUM_ID_1);
+        addAlbumMedia(mLocalMediaGenerator, LOCAL_ONLY_2.first, LOCAL_ONLY_2.second,
+                ALBUM_ID_2);
+
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 2. Set secondary cloud provider
+        mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 3. Set primary cloud provider
+        mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+        // 4. Set secondary cloud provider again
+        mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 4. Set primary cloud provider again
+        mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+
+        // 4a. Sync the first album and query local albums
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 4b. Sync the second album
+        mController.syncAlbumMedia(ALBUM_ID_2);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_2, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
+        }
+
+        // 5. Sync and query cloud albums
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+        // 6. Clear cloud provider
+        mController.setCloudProvider(/* authority */ null);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
     }
 
     @Test
@@ -231,7 +387,7 @@
 
         // 1. Do nothing
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 2. Add cloud-only item
         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
@@ -246,7 +402,7 @@
         // 3. Set invalid cloud version
         mCloudPrimaryMediaGenerator.setMediaCollectionId(/* version */ null);
         mController.syncAllMedia();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 4. Set valid cloud version
         mCloudPrimaryMediaGenerator.setMediaCollectionId(COLLECTION_1);
@@ -259,10 +415,53 @@
         }
     }
 
+
+    @Test
+    public void testCloudResetAlbumMediaSync() {
+        mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+
+        // 1. Do nothing
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 2. Add cloud-only item
+        addAlbumMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1.first, CLOUD_ONLY_1.second,
+                ALBUM_ID_1);
+
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+        // 3. Reset Cloud provider
+        mCloudPrimaryMediaGenerator.resetAll();
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+
+        // 4. Add cloud-only item
+        addAlbumMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1.first, CLOUD_ONLY_1.second,
+                ALBUM_ID_1);
+
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID_1, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+
+            assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+        }
+
+
+        // 5. Unset Cloud provider
+        mController.setCloudProvider(null);
+        mController.syncAlbumMedia(ALBUM_ID_1);
+        assertEmptyCursorFromAlbumMediaQuery(ALBUM_ID_1, false);
+    }
+
     @Test
     public void testSyncAllMediaCloudAndLocal() {
         // 1. Do nothing
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 2. Set primary cloud provider and add 2 items: cloud+local and local-only
         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
@@ -311,7 +510,7 @@
         deleteMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
         mController.syncAllMedia();
 
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
     }
 
     @Test
@@ -404,7 +603,7 @@
         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
         controller.notifyMediaEvent();
         waitForIdle();
-        assertEmptyCursor();
+        assertEmptyCursorFromMediaQuery();
 
         // 2. Sleep for delay
         SystemClock.sleep(SYNC_DELAY_MS);
@@ -601,6 +800,11 @@
         generator.addMedia(media.first, media.second);
     }
 
+    private static void addAlbumMedia(MediaGenerator generator, String localId, String cloudId,
+            String albumId) {
+        generator.addAlbumMedia(localId, cloudId, albumId);
+    }
+
     private static void addMedia(MediaGenerator generator, Pair<String, String> media,
             String albumId, String mimeType, int standardMimeTypeExtension, long sizeBytes,
             boolean isFavorite) {
@@ -622,12 +826,23 @@
                 new PickerDbFacade.QueryFilterBuilder(1000).build());
     }
 
-    private void assertEmptyCursor() {
+    private Cursor queryAlbumMedia(String albumId, boolean isLocal) {
+        return mFacade.queryAlbumMediaForUi(
+                new PickerDbFacade.QueryFilterBuilder(1000).setAlbumId(albumId).build(), isLocal);
+    }
+
+    private void assertEmptyCursorFromMediaQuery() {
         try (Cursor cr = queryMedia()) {
             assertThat(cr.getCount()).isEqualTo(0);
         }
     }
 
+    private void assertEmptyCursorFromAlbumMediaQuery(String albumId, boolean isLocal) {
+        try (Cursor cr = queryAlbumMedia(albumId, isLocal)) {
+            assertThat(cr.getCount()).isEqualTo(0);
+        }
+    }
+
     private static void assertCursor(Cursor cursor, String id, String expectedAuthority) {
         cursor.moveToNext();
         assertThat(cursor.getString(cursor.getColumnIndex(MediaColumns.ID)))
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
index 28d1bab..d35d4ed 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDatabaseHelperTest.java
@@ -37,13 +37,14 @@
 public class PickerDatabaseHelperTest {
     private static final String TAG = "PickerDatabaseHelperTest";
 
-    private static final String SQLITE_MASTER_ORDER_BY = "type,name,tbl_name";
     private static final String TEST_PICKER_DB = "test_picker";
     static final String MEDIA_TABLE = "media";
+    static final String ALBUM_MEDIA_TABLE = "album_media";
 
     private static final String KEY_LOCAL_ID = "local_id";
     private static final String KEY_CLOUD_ID = "cloud_id";
     private static final String KEY_IS_VISIBLE = "is_visible";
+    private static final String KEY_ALBUM_ID = "album_id";
     private static final String KEY_DATE_TAKEN_MS = "date_taken_ms";
     private static final String KEY_SYNC_GENERATION = "sync_generation";
     private static final String KEY_SIZE_BYTES = "size_bytes";
@@ -56,6 +57,7 @@
     private static final long DATE_TAKEN_MS = 1623852851911L;
     private static final long GENERATION_MODIFIED = 1L;
     private static final String CLOUD_ID = "asdfghjkl;";
+    private static final String ALBUM_ID = "testAlbum;";
     private static final String MIME_TYPE = "video/mp4";
     private static final int STANDARD_MIME_TYPE_EXTENSION =
             CloudMediaProviderContract.MediaColumns.STANDARD_MIME_TYPE_EXTENSION_GIF;
@@ -70,7 +72,7 @@
     }
 
     @Test
-    public void testColumns() throws Exception {
+    public void testMediaColumns() throws Exception {
         String[] projection = new String[] {
             KEY_LOCAL_ID,
             KEY_CLOUD_ID,
@@ -110,6 +112,48 @@
         }
     }
 
+
+    @Test
+    public void testAlbumMediaColumns() throws Exception {
+        String[] projection = new String[] {
+                KEY_LOCAL_ID,
+                KEY_CLOUD_ID,
+                KEY_ALBUM_ID,
+                KEY_DATE_TAKEN_MS,
+                KEY_SYNC_GENERATION,
+                KEY_SIZE_BYTES,
+                KEY_DURATION_MS,
+                KEY_MIME_TYPE,
+                KEY_STANDARD_MIME_TYPE_EXTENSION
+        };
+
+        try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
+            SQLiteDatabase db = helper.getWritableDatabase();
+
+            // All fields specified
+            ContentValues values = getBasicContentValues();
+            values.put(KEY_LOCAL_ID, LOCAL_ID);
+            values.put(KEY_ALBUM_ID, ALBUM_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isNotEqualTo(-1);
+
+            try (Cursor cr = db.query(ALBUM_MEDIA_TABLE, projection, null, null, null, null,
+                    null)) {
+                assertThat(cr.getCount()).isEqualTo(1);
+                while (cr.moveToNext()) {
+                    assertThat(cr.getLong(0)).isEqualTo(LOCAL_ID);
+                    assertThat(cr.getString(1)).isEqualTo(null);
+                    assertThat(cr.getString(2)).isEqualTo(ALBUM_ID);
+                    assertThat(cr.getLong(3)).isEqualTo(DATE_TAKEN_MS);
+                    assertThat(cr.getLong(4)).isEqualTo(GENERATION_MODIFIED);
+                    assertThat(cr.getLong(5)).isEqualTo(SIZE_BYTES);
+                    assertThat(cr.getLong(6)).isEqualTo(DURATION_MS);
+                    assertThat(cr.getString(7)).isEqualTo(MIME_TYPE);
+                    assertThat(cr.getInt(8)).isEqualTo(STANDARD_MIME_TYPE_EXTENSION);
+                }
+            }
+        }
+    }
+
     @Test
     public void testCheck_cloudOrLocal() throws Exception {
         try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
@@ -156,6 +200,37 @@
     }
 
     @Test
+    public void testUniqueConstraintAlbumMedia() throws Exception {
+        try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
+            SQLiteDatabase db = helper.getWritableDatabase();
+
+            // Local Album Media
+            ContentValues values = getBasicContentValues();
+            values.put(KEY_LOCAL_ID, LOCAL_ID);
+            values.put(KEY_ALBUM_ID, ALBUM_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isNotEqualTo(-1);
+
+            // Another local for Album Media
+            values = getBasicContentValues();
+            values.put(KEY_LOCAL_ID, LOCAL_ID);
+            values.put(KEY_ALBUM_ID, ALBUM_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // Cloud for Album Media
+            values = getBasicContentValues();
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            values.put(KEY_ALBUM_ID, ALBUM_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isNotEqualTo(-1);
+
+            // Another Cloud for Album Media
+            values = getBasicContentValues();
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            values.put(KEY_ALBUM_ID, ALBUM_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
+        }
+    }
+
+    @Test
     public void testUniqueConstraint_cloud() throws Exception {
         try (PickerDatabaseHelper helper = new PickerDatabaseHelperT(sIsolatedContext)) {
             SQLiteDatabase db = helper.getWritableDatabase();
@@ -240,6 +315,18 @@
             values.put(KEY_SIZE_BYTES, 0);
             values.put(KEY_CLOUD_ID, CLOUD_ID);
             assertThat(db.insert(MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // size_bytes=NULL for Album Media Table
+            values = getBasicContentValues();
+            values.remove(KEY_SIZE_BYTES);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // size_bytes=0 for Album Media Table
+            values = getBasicContentValues();
+            values.put(KEY_SIZE_BYTES, 0);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
         }
     }
 
@@ -253,6 +340,12 @@
             values.remove(KEY_MIME_TYPE);
             values.put(KEY_CLOUD_ID, CLOUD_ID);
             assertThat(db.insert(MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // mime_type=NULL for Album Media
+            values = getBasicContentValues();
+            values.remove(KEY_MIME_TYPE);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
         }
     }
 
@@ -272,6 +365,18 @@
             values.put(KEY_DATE_TAKEN_MS, -1);
             values.put(KEY_CLOUD_ID, CLOUD_ID);
             assertThat(db.insert(MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // date_taken_ms=NULL for Album Media
+            values = getBasicContentValues();
+            values.remove(KEY_DATE_TAKEN_MS);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // date_taken_ms=-1 for Album Media
+            values = getBasicContentValues();
+            values.put(KEY_DATE_TAKEN_MS, -1);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
         }
     }
 
@@ -291,6 +396,18 @@
             values.put(KEY_SYNC_GENERATION, -1);
             values.put(KEY_CLOUD_ID, CLOUD_ID);
             assertThat(db.insert(MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // generation_modified=NULL for Album Media
+            values = getBasicContentValues();
+            values.remove(KEY_SYNC_GENERATION);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // generation_modified=-1 for Album Media
+            values = getBasicContentValues();
+            values.put(KEY_SYNC_GENERATION, -1);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
         }
     }
 
@@ -304,6 +421,12 @@
             values.put(KEY_DURATION_MS, -1);
             values.put(KEY_CLOUD_ID, CLOUD_ID);
             assertThat(db.insert(MEDIA_TABLE, null, values)).isEqualTo(-1);
+
+            // duration=-1
+            values = getBasicContentValues();
+            values.put(KEY_DURATION_MS, -1);
+            values.put(KEY_CLOUD_ID, CLOUD_ID);
+            assertThat(db.insert(ALBUM_MEDIA_TABLE, null, values)).isEqualTo(-1);
         }
     }
 
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
index 5173e74..ec92030 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerDbFacadeTest.java
@@ -46,6 +46,7 @@
     private static final long DURATION_MS = 5;
     private static final String LOCAL_ID = "50";
     private static final String CLOUD_ID = "asdfghjkl;";
+    private static final String ALBUM_ID = "testAlbum";
     private static final String VIDEO_MIME_TYPE = "video/mp4";
     private static final String IMAGE_MIME_TYPE = "image/jpeg";
     private static final int STANDARD_MIME_TYPE_EXTENSION =
@@ -67,7 +68,7 @@
     }
 
     @Test
-    public void testAddLocalOnly() throws Exception {
+    public void testAddLocalOnlyMedia() throws Exception {
         Cursor cursor1 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 1);
         Cursor cursor2 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 2);
 
@@ -156,6 +157,54 @@
     }
 
     @Test
+    public void testAddLocalAlbumMedia() {
+        Cursor cursor1 = getAlbumMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 1, true);
+        Cursor cursor2 = getAlbumMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 2, true);
+
+        assertAddAlbumMediaOperation(LOCAL_PROVIDER, cursor1, 1, ALBUM_ID);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+            cr.moveToFirst();
+            assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 1);
+        }
+
+        // Test updating the same row. We always do a full sync for album media files.
+        assertResetAlbumMediaOperation(LOCAL_PROVIDER, 1, ALBUM_ID);
+        assertAddAlbumMediaOperation(LOCAL_PROVIDER, cursor2, 1, ALBUM_ID);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID, true)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+            cr.moveToFirst();
+            assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 2);
+        }
+    }
+
+    @Test
+    public void testAddCloudAlbumMedia() {
+        Cursor cursor1 = getAlbumMediaCursor(CLOUD_ID, DATE_TAKEN_MS + 1, false);
+        Cursor cursor2 = getAlbumMediaCursor(CLOUD_ID, DATE_TAKEN_MS + 2, false);
+
+        assertAddAlbumMediaOperation(CLOUD_PROVIDER, cursor1, 1, ALBUM_ID);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+            cr.moveToFirst();
+            assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 1);
+        }
+
+        // Test updating the same row. We always do a full sync for album media files.
+        assertResetAlbumMediaOperation(CLOUD_PROVIDER, 1, ALBUM_ID);
+        assertAddAlbumMediaOperation(CLOUD_PROVIDER, cursor2, 1, ALBUM_ID);
+
+        try (Cursor cr = queryAlbumMedia(ALBUM_ID, false)) {
+            assertThat(cr.getCount()).isEqualTo(1);
+            cr.moveToFirst();
+            assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 2);
+        }
+    }
+
+    @Test
     public void testRemoveLocal() throws Exception {
         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
 
@@ -946,6 +995,11 @@
                 new PickerDbFacade.QueryFilterBuilder(1000).build());
     }
 
+    private Cursor queryAlbumMedia(String albumId, boolean isLocal) {
+        return mFacade.queryAlbumMediaForUi(
+                new PickerDbFacade.QueryFilterBuilder(1000).setAlbumId(albumId).build(), isLocal);
+    }
+
     private void assertAddMediaOperation(String authority, Cursor cursor, int writeCount) {
         try (PickerDbFacade.DbWriteOperation operation =
                      mFacade.beginAddMediaOperation(authority)) {
@@ -954,6 +1008,15 @@
         }
     }
 
+    private void assertAddAlbumMediaOperation(String authority, Cursor cursor, int writeCount,
+            String albumId) {
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mFacade.beginAddAlbumMediaOperation(authority, albumId)) {
+            assertWriteOperation(operation, cursor, writeCount);
+            operation.setSuccess();
+        }
+    }
+
     private void assertRemoveMediaOperation(String authority, Cursor cursor, int writeCount) {
         try (PickerDbFacade.DbWriteOperation operation =
                      mFacade.beginRemoveMediaOperation(authority)) {
@@ -970,6 +1033,15 @@
         }
     }
 
+    private void assertResetAlbumMediaOperation(String authority, int writeCount,
+            String albumId) {
+        try (PickerDbFacade.DbWriteOperation operation =
+                     mFacade.beginResetAlbumMediaOperation(authority, albumId)) {
+            assertWriteOperation(operation, null, writeCount);
+            operation.setSuccess();
+        }
+    }
+
     private static void assertWriteOperation(PickerDbFacade.DbWriteOperation operation,
             Cursor cursor, int expectedWriteCount) {
         final int writeCount = operation.execute(cursor);
@@ -1016,12 +1088,47 @@
         return c;
     }
 
+    private static Cursor getAlbumMediaCursor(String id, long dateTakenMs, long generationModified,
+            String mediaStoreUri, long sizeBytes, String mimeType, int standardMimeTypeExtension) {
+        String[] projectionKey = new String[] {
+                MediaColumns.ID,
+                MediaColumns.MEDIA_STORE_URI,
+                MediaColumns.DATE_TAKEN_MILLIS,
+                MediaColumns.SYNC_GENERATION,
+                MediaColumns.SIZE_BYTES,
+                MediaColumns.MIME_TYPE,
+                MediaColumns.STANDARD_MIME_TYPE_EXTENSION,
+                MediaColumns.DURATION_MILLIS,
+        };
+
+        String[] projectionValue = new String[] {
+                id,
+                mediaStoreUri,
+                String.valueOf(dateTakenMs),
+                String.valueOf(generationModified),
+                String.valueOf(sizeBytes),
+                mimeType,
+                String.valueOf(standardMimeTypeExtension),
+                String.valueOf(DURATION_MS)
+        };
+
+        MatrixCursor c = new MatrixCursor(projectionKey);
+        c.addRow(projectionValue);
+        return c;
+    }
+
     private static Cursor getLocalMediaCursor(String localId, long dateTakenMs) {
         return getMediaCursor(localId, dateTakenMs, GENERATION_MODIFIED, toMediaStoreUri(localId),
                 SIZE_BYTES, VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION,
                 /* isFavorite */ false);
     }
 
+    private static Cursor getAlbumMediaCursor(String mediaId, long dateTakenMs, boolean isLocal) {
+        return getAlbumMediaCursor(mediaId, dateTakenMs, GENERATION_MODIFIED,
+                isLocal ? toMediaStoreUri(mediaId) : null,
+                SIZE_BYTES, VIDEO_MIME_TYPE, STANDARD_MIME_TYPE_EXTENSION);
+    }
+
     private static Cursor getCloudMediaCursor(String cloudId, String localId,
             long dateTakenMs) {
         return getMediaCursor(cloudId, dateTakenMs, GENERATION_MODIFIED, toMediaStoreUri(localId),
diff --git a/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java b/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
index 208cb55..46fa708 100644
--- a/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/PickerResultTest.java
@@ -22,10 +22,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 import android.content.ClipData;
 import android.content.ContentUris;
 import android.content.Context;
@@ -39,7 +35,6 @@
 
 import com.android.providers.media.PickerUriResolver;
 import com.android.providers.media.photopicker.PickerSyncController;
-import com.android.providers.media.photopicker.data.PickerDbFacade;
 import com.android.providers.media.photopicker.data.model.Item;
 
 import org.junit.Before;
@@ -71,8 +66,7 @@
         List<Item> items = null;
         try {
             items = createItemSelection(1);
-            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri(),
-                    items.get(0).getId());
+            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri());
             final Intent intent = PickerResult.getPickerResponseIntent(
                     /* canSelectMultiple */ false, items);
 
@@ -103,8 +97,7 @@
             items = createItemSelection(itemCount);
             List<Uri> expectedPickerUris = new ArrayList<>();
             for (Item item: items) {
-                expectedPickerUris.add(PickerResult.getPickerUri(item.getContentUri(),
-                        item.getId()));
+                expectedPickerUris.add(PickerResult.getPickerUri(item.getContentUri()));
             }
             final Intent intent = PickerResult.getPickerResponseIntent(/* canSelectMultiple */ true,
                     items);
@@ -133,8 +126,7 @@
         try {
             final int itemCount = 1;
             items = createItemSelection(itemCount);
-            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri(),
-                    items.get(0).getId());
+            final Uri expectedPickerUri = PickerResult.getPickerUri(items.get(0).getContentUri());
             final Intent intent = PickerResult.getPickerResponseIntent(/* canSelectMultiple */ true,
                     items);
 
@@ -172,14 +164,12 @@
         // Create an image and revoke test app's access on it
         Uri imageUri = assertCreateNewImage();
         clearMediaOwner(imageUri, mContext.getUserId());
-        if (PickerDbFacade.isPickerDbEnabled()) {
-            // Create with a picker URI with picker db enabled
-            imageUri = PickerUriResolver
-                    .getMediaUri(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)
-                    .buildUpon()
-                    .appendPath(String.valueOf(ContentUris.parseId(imageUri)))
-                    .build();
-        }
+        // Create with a picker URI with picker db enabled
+        imageUri = PickerUriResolver
+                .getMediaUri(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY)
+                .buildUpon()
+                .appendPath(String.valueOf(ContentUris.parseId(imageUri)))
+                .build();
 
         return new Item(imageUri.getLastPathSegment(), "image/jpeg", /* dateTaken */ 0,
                 /* generationModified */ 0, /* duration */ 0, imageUri, _SPECIAL_FORMAT_NONE);
diff --git a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
index 0800b9c..097a668 100644
--- a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
@@ -25,17 +25,22 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.MediaStore;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.LocalDate;
+import java.time.ZoneId;
+
 @RunWith(AndroidJUnit4.class)
 public class ItemTest {
 
@@ -46,9 +51,8 @@
         final long generationModified = 1L;
         final String mimeType = "image/png";
         final long duration = 1000;
-        final int specialFormat = _SPECIAL_FORMAT_NONE;
         final Cursor cursor = generateCursorForItem(id, mimeType, dateTaken, generationModified,
-                duration, specialFormat);
+                duration, _SPECIAL_FORMAT_NONE);
         cursor.moveToFirst();
 
         final Item item = new Item(cursor, UserId.CURRENT_USER);
@@ -74,9 +78,8 @@
         final long generationModified = 1L;
         final String mimeType = "image/png";
         final long duration = 1000;
-        final int specialFormat = _SPECIAL_FORMAT_NONE;
         final Cursor cursor = generateCursorForItem(id, mimeType, dateTaken, generationModified,
-                duration, specialFormat);
+                duration, _SPECIAL_FORMAT_NONE);
         cursor.moveToFirst();
         final UserId userId = UserId.of(UserHandle.of(10));
 
@@ -246,10 +249,43 @@
         assertThat(item2SameValues.compareTo(item2)).isEqualTo(0);
     }
 
+    @Test
+    public void testGetContentDescription() {
+        final String id = "1";
+        final long dateTaken = LocalDate.of(2020 /* year */, 7 /* month */, 7 /* dayOfMonth */)
+                .atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        final long generationModified = 1L;
+        final long duration = 1000;
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        Item item = generateItem(id, "image/jpeg", dateTaken, generationModified, duration);
+        assertThat(item.getContentDescription(context))
+                .isEqualTo("Photo taken on Jul 7, 2020, 12:00:00 AM");
+
+        item = generateItem(id, "video/mp4", dateTaken, generationModified, duration);
+        assertThat(item.getContentDescription(context))
+                .isEqualTo("Video taken on Jul 7, 2020, 12:00:00 AM");
+
+        item = generateSpecialFormatItem(id, "image/gif", dateTaken, generationModified, duration,
+                _SPECIAL_FORMAT_GIF);
+        assertThat(item.getContentDescription(context))
+                .isEqualTo("GIF taken on Jul 7, 2020, 12:00:00 AM");
+
+        item = generateSpecialFormatItem(id, "image/webp", dateTaken, generationModified, duration,
+                _SPECIAL_FORMAT_ANIMATED_WEBP);
+        assertThat(item.getContentDescription(context))
+                .isEqualTo("GIF taken on Jul 7, 2020, 12:00:00 AM");
+
+        item = generateSpecialFormatItem(id, "image/jpeg", dateTaken, generationModified, duration,
+                _SPECIAL_FORMAT_MOTION_PHOTO);
+        assertThat(item.getContentDescription(context))
+                .isEqualTo("Motion Photo taken on Jul 7, 2020, 12:00:00 AM");
+    }
+
     private static Cursor generateCursorForItem(String id, String mimeType, long dateTaken,
             long generationModified, long duration, int specialFormat) {
         final MatrixCursor cursor = new MatrixCursor(ItemColumns.ALL_COLUMNS);
-        cursor.addRow(new Object[] {id, mimeType, dateTaken, /* dateModified */ dateTaken,
+        cursor.addRow(new Object[] {id, mimeType, dateTaken, dateTaken /* dateModified */,
                 generationModified, duration, specialFormat});
         return cursor;
     }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/ActiveProfileButtonTest.java b/tests/src/com/android/providers/media/photopicker/espresso/ActiveProfileButtonTest.java
index 63af436..e96d45c 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/ActiveProfileButtonTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/ActiveProfileButtonTest.java
@@ -65,7 +65,7 @@
         onView(withId(PROFILE_BUTTON)).check(matches(isDisplayed()));
 
         // Goto Albums page
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
         // Verify profile button is displayed
         onView(withId(PROFILE_BUTTON)).check(matches(isDisplayed()));
@@ -80,13 +80,13 @@
         onView(withContentDescription("Navigate up")).perform(click());
 
         // on clicking back button we are back to Album grid
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
         // Verify profile button is displayed
         onView(withId(PROFILE_BUTTON)).check(matches(isDisplayed()));
 
         // Goto Photos grid
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
         // Verify profile button is displayed
         onView(withId(PROFILE_BUTTON)).check(matches(isDisplayed()));
@@ -115,7 +115,7 @@
         onView(withId(PROFILE_BUTTON)).check(matches(not(isDisplayed())));
 
         // Goto Albums page and verify profile button is not shown
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
         onView(withId(PROFILE_BUTTON)).check(matches(not(isDisplayed())));
     }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java b/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
index 4b0ac8b..4e6b6d3 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
@@ -31,6 +31,7 @@
 
 import static org.hamcrest.Matchers.allOf;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
 
@@ -52,19 +53,22 @@
     @Test
     public void testAlbumGrid() {
         // Goto Albums page
-        onView(allOf(withText(R.string.picker_albums), withParent(withId(R.id.chip_container))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         // Verify that toolbar has correct components
-        onView(withId(CHIP_CONTAINER_ID)).check(matches((isDisplayed())));
-        // Photos chip
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(withId(TAB_LAYOUT_ID)).check(matches((isDisplayed())));
+        // Photos tab
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches((isDisplayed())));
-        // Albums chip
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        // Albums tab
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches((isDisplayed())));
-        // Navigate up button
-        onView(withContentDescription("Navigate up")).check(matches((isDisplayed())));
+        // Cancel button
+        final String cancelString =
+                InstrumentationRegistry.getTargetContext().getResources().getString(
+                        android.R.string.cancel);
+        onView(withContentDescription(cancelString)).check(matches((isDisplayed())));
 
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java b/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
index f207436..120d796 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
@@ -36,7 +36,6 @@
 import org.hamcrest.Matcher;
 
 public class CustomSwipeAction {
-    private static final int PREVIEW_VIEW_PAGER_ID = R.id.preview_viewPager;
 
     /**
      * A custom swipeLeft method to avoid system gestures taking over ViewActions#swipeLeft
@@ -46,8 +45,8 @@
                 GeneralLocation.CENTER_LEFT, Press.FINGER);
     }
 
-    public static void swipeLeftAndWait() {
-        onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(customSwipeLeft());
+    public static void swipeLeftAndWait(int viewId) {
+        onView(withId(viewId)).perform(customSwipeLeft());
         Espresso.onIdle();
     }
 
@@ -59,9 +58,9 @@
                 GeneralLocation.CENTER_RIGHT, Press.FINGER);
     }
 
-    public static void swipeRightAndWait() {
+    public static void swipeRightAndWait(int viewId) {
         // Use customSwipeRight to avoid system gestures taking over ViewActions#swipeRight
-        onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(customSwipeRight());
+        onView(withId(viewId)).perform(customSwipeRight());
         Espresso.onIdle();
     }
 
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/MimeTypeFilterTest.java b/tests/src/com/android/providers/media/photopicker/espresso/MimeTypeFilterTest.java
index bf9b95a..ce0c612 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/MimeTypeFilterTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/MimeTypeFilterTest.java
@@ -67,7 +67,7 @@
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Go to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         // Only two albums, Camera and Downloads
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/MultiSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/MultiSelectTest.java
index cce4b93..cc1d7c0 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/MultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/MultiSelectTest.java
@@ -151,7 +151,7 @@
     @Test
     public void testMultiSelectAcrossCategories() {
         // Navigate to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         final int cameraStringId = R.string.picker_category_camera;
@@ -172,7 +172,7 @@
         onView(withContentDescription("Navigate up")).perform(click());
 
         // On clicking back button we are back to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
 
         // Navigate to photos in Video album
@@ -195,7 +195,7 @@
         assertItemSelected(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_CHECK_ID);
 
         // Navigate to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         final int cameraStringId = R.string.picker_category_camera;
@@ -215,13 +215,13 @@
         onView(withContentDescription("Navigate up")).perform(click());
 
         // On clicking back button we are back to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
         onView(allOf(withText(cameraStringId),
                 isDescendantOfA(withId(PICKER_TAB_RECYCLERVIEW_ID)))).check(matches(isDisplayed()));
 
         // Navigate to Photos tab
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         // The image item is not selected
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/NoItemsTest.java b/tests/src/com/android/providers/media/photopicker/espresso/NoItemsTest.java
index 9efdbcf..8b55975 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/NoItemsTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/NoItemsTest.java
@@ -19,6 +19,7 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withParent;
@@ -63,8 +64,8 @@
             onView(withText(R.string.picker_photos_empty_message)).check(matches(isDisplayed()));
 
             // Goto Albums page
-            onView(allOf(withText(R.string.picker_albums), withParent(withId(R.id.chip_container))))
-                    .perform(click());
+            onView(allOf(withText(R.string.picker_albums),
+                    isDescendantOfA(withId(R.id.tab_layout)))).perform(click());
 
             onView(withId(pickerTabRecyclerViewId)).check(matches(not(isDisplayed())));
             onView(withId(android.R.id.empty)).check(matches(isDisplayed()));
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
index 95ea816..38fa0ee 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
@@ -19,19 +19,18 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.hasChildCount;
-import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
 import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isNotSelected;
 import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.providers.media.photopicker.espresso.BottomSheetTestUtils.assertBottomSheetState;
 import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.customSwipeDownPartialScreen;
+import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.swipeLeftAndWait;
+import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.swipeRightAndWait;
 import static com.android.providers.media.photopicker.espresso.RecyclerViewMatcher.withRecyclerView;
 
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
@@ -43,10 +42,12 @@
 
 import android.app.Activity;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.espresso.IdlingRegistry;
 import androidx.test.espresso.action.ViewActions;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.viewpager2.widget.ViewPager2;
 
 import com.android.providers.media.R;
 
@@ -57,6 +58,9 @@
 
 @RunWith(AndroidJUnit4ClassRunner.class)
 public class PhotoPickerActivityTest extends PhotoPickerBaseTest {
+
+    private static final int TAB_VIEW_PAGER_ID = R.id.picker_tab_viewpager;
+
     @Rule
     public ActivityScenarioRule<PhotoPickerTestActivity> mRule
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
@@ -71,13 +75,16 @@
         onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
         onView(withId(PRIVACY_TEXT_ID)).check(matches(isDisplayed()));
         onView(withId(android.R.id.empty)).check(matches(not(isDisplayed())));
-        onView(withContentDescription("Navigate up")).perform(click());
+
+        final String cancelString =
+                InstrumentationRegistry.getTargetContext().getResources().getString(
+                        android.R.string.cancel);
+        onView(withContentDescription(cancelString)).perform(click());
         assertThat(mRule.getScenario().getResult().getResultCode()).isEqualTo(
                 Activity.RESULT_CANCELED);
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testDoesNotShowProfileButton() {
         // Register bottom sheet idling resource so that we don't read bottom sheet state when
         // in between changing states
@@ -105,7 +112,7 @@
             onView(withId(R.id.profile_button)).check(matches(not(isDisplayed())));
 
             // Navigate to Albums tab
-            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                     .perform(click());
             onView(withId(R.id.profile_button)).check(matches(not(isDisplayed())));
 
@@ -119,7 +126,7 @@
             onView(withContentDescription("Navigate up")).perform(click());
 
             // on clicking back button we are back to Album grid
-            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                     .check(matches(isSelected()));
             onView(withId(R.id.profile_button)).check(matches(not(isDisplayed())));
         } finally {
@@ -128,7 +135,6 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testBottomSheetState() {
         // Register bottom sheet idling resource so that we don't read bottom sheet state when
         // in between changing states
@@ -175,48 +181,105 @@
     public void testToolbarLayout() {
         onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
 
-        onView(withId(CHIP_CONTAINER_ID)).check(matches(isDisplayed()));
-        onView(withId(CHIP_CONTAINER_ID)).check(matches(hasChildCount(2)));
+        onView(withId(TAB_LAYOUT_ID)).check(matches(isDisplayed()));
+
+        mRule.getScenario().onActivity(activity -> {
+            final ViewPager2 viewPager2 = activity.findViewById(TAB_VIEW_PAGER_ID);
+            assertThat(viewPager2.getAdapter().getItemCount()).isEqualTo(2);
+        });
 
         onView(allOf(withText(PICKER_PHOTOS_STRING_ID),
-                isDescendantOfA(withId(CHIP_CONTAINER_ID)))).check(matches(isDisplayed()));
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID),
-                isDescendantOfA(withId(CHIP_CONTAINER_ID)))).check(matches(isClickable()));
-
+                isDescendantOfA(withId(TAB_LAYOUT_ID)))).check(matches(isDisplayed()));
         onView(allOf(withText(PICKER_ALBUMS_STRING_ID),
-                isDescendantOfA(withId(CHIP_CONTAINER_ID)))).check(matches(isDisplayed()));
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID),
-                isDescendantOfA(withId(CHIP_CONTAINER_ID)))).check(matches(isClickable()));
+                isDescendantOfA(withId(TAB_LAYOUT_ID)))).check(matches(isDisplayed()));
 
         // TODO(b/200513333): Check close icon
     }
 
     @Test
-    public void testTabChipNavigation() {
-        onView(withId(CHIP_CONTAINER_ID)).check(matches(isDisplayed()));
+    public void testTabNavigation() {
+        onView(withId(TAB_LAYOUT_ID)).check(matches(isDisplayed()));
 
-        // On clicking albums tab, we should see albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        // On clicking albums tab item, we should see albums tab
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isNotSelected()));
         // Verify Camera album is shown, we are in albums tab
         onView(allOf(withText(R.string.picker_category_camera),
                 isDescendantOfA(withId(PICKER_TAB_RECYCLERVIEW_ID)))).check(matches(isDisplayed()));
 
 
-        // On clicking photos tab chip, we should see photos tab
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        // On clicking photos tab item, we should see photos tab
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isNotSelected()));
         // Verify first item is recent header, we are in photos tab
         onView(withRecyclerView(PICKER_TAB_RECYCLERVIEW_ID)
                 .atPositionOnView(0, R.id.date_header_title))
                 .check(matches(withText(R.string.recent)));
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testTabSwiping() throws Exception {
+        onView(withId(TAB_LAYOUT_ID)).check(matches(isDisplayed()));
+
+        // If we want to swipe the viewPager2 of tabContainerFragment in Espresso tests, at least 90
+        // percent of the view's area is displayed to the user. Swipe up the bottom Sheet to make
+        // sure it is in full Screen mode.
+        // Register bottom sheet idling resource so that we don't read bottom sheet state when
+        // in between changing states
+        final BottomSheetIdlingResource bottomSheetIdlingResource =
+                BottomSheetIdlingResource.register(mRule);
+
+        try {
+            // Single select PhotoPicker is launched in partial screen mode
+            bottomSheetIdlingResource.setExpectedState(STATE_COLLAPSED);
+            mRule.getScenario().onActivity(activity -> {
+                assertBottomSheetState(activity, STATE_COLLAPSED);
+            });
+
+            // Swipe up and check that the PhotoPicker is in full screen mode.
+            bottomSheetIdlingResource.setExpectedState(STATE_EXPANDED);
+            onView(withId(PRIVACY_TEXT_ID)).check(matches(isDisplayed()));
+            onView(withId(PRIVACY_TEXT_ID)).perform(ViewActions.swipeUp());
+            mRule.getScenario().onActivity(activity -> {
+                assertBottomSheetState(activity, STATE_EXPANDED);
+            });
+        } finally {
+            IdlingRegistry.getInstance().unregister(bottomSheetIdlingResource);
+        }
+
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, TAB_VIEW_PAGER_ID)) {
+            // Swipe left, we should see albums tab
+            swipeLeftAndWait(TAB_VIEW_PAGER_ID);
+
+            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
+                    .check(matches(isSelected()));
+            onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
+                    .check(matches(isNotSelected()));
+            // Verify Camera album is shown, we are in albums tab
+            onView(allOf(withText(R.string.picker_category_camera),
+                    isDescendantOfA(withId(PICKER_TAB_RECYCLERVIEW_ID)))).check(
+                    matches(isDisplayed()));
+
+            // Swipe right, we should see photos tab
+            swipeRightAndWait(TAB_VIEW_PAGER_ID);
+
+            onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
+                    .check(matches(isSelected()));
+            onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
+                    .check(matches(isNotSelected()));
+            // Verify first item is recent header, we are in photos tab
+            onView(withRecyclerView(PICKER_TAB_RECYCLERVIEW_ID)
+                    .atPositionOnView(0, R.id.date_header_title))
+                    .check(matches(withText(R.string.recent)));
+        }
+    }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
index 37aa099..f2c80e7 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
@@ -54,7 +54,7 @@
 
 public class PhotoPickerBaseTest {
     protected static final int PICKER_TAB_RECYCLERVIEW_ID = R.id.picker_tab_recyclerview;
-    protected static final int CHIP_CONTAINER_ID = R.id.chip_container;
+    protected static final int TAB_LAYOUT_ID = R.id.tab_layout;
     protected static final int PICKER_PHOTOS_STRING_ID = R.string.picker_photos;
     protected static final int PICKER_ALBUMS_STRING_ID = R.string.picker_albums;
     protected static final int PREVIEW_VIEW_PAGER_ID = R.id.preview_viewPager;
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
index 61bd82d..dd901a2 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
@@ -16,7 +16,6 @@
 
 package com.android.providers.media.photopicker.espresso;
 
-import static androidx.test.InstrumentationRegistry.getTargetContext;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
@@ -59,7 +58,6 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testPhotoGridLayout_photoGrid() {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
@@ -76,7 +74,6 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testPhotoGridLayout_image() {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
@@ -92,7 +89,6 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testPhotoGridLayout_video() {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
@@ -117,7 +113,7 @@
     @Test
     public void testPhotoGrid_albumPhotos() {
         // Navigate to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         final int cameraStringId = R.string.picker_category_camera;
@@ -129,8 +125,8 @@
         onView(allOf(withText(cameraStringId), withParent(withId(R.id.toolbar))))
                 .check(matches(isDisplayed()));
 
-        // Verify that tab chips are not shown on the toolbar
-        onView(withId(CHIP_CONTAINER_ID)).check(matches(not(isDisplayed())));
+        // Verify that tab tabs are not shown on the toolbar
+        onView(withId(TAB_LAYOUT_ID)).check(matches(not(isDisplayed())));
 
         // Verify that privacy text is not shown
         onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
@@ -149,8 +145,8 @@
         // Verify that first item is TODAY
         onView(withRecyclerView(PICKER_TAB_RECYCLERVIEW_ID)
                 .atPositionOnView(0, dateHeaderTitleId))
-                .check(matches(
-                        withText(DateTimeUtils.getDateTimeString(System.currentTimeMillis()))));
+                .check(matches(withText(
+                        DateTimeUtils.getDateHeaderString(System.currentTimeMillis()))));
 
         final int photoItemPosition = 1;
         // Verify first item is image and has no other icons other than thumbnail
@@ -167,7 +163,7 @@
         onView(withContentDescription("Navigate up")).perform(click());
 
         // on clicking back button we are back to Album grid
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
         onView(allOf(withText(cameraStringId),
                 isDescendantOfA(withId(PICKER_TAB_RECYCLERVIEW_ID)))).check(matches(isDisplayed()));
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java
index 5280c34..2fbba6c 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java
@@ -41,8 +41,6 @@
 import android.widget.Button;
 
 import androidx.lifecycle.ViewModelProvider;
-import androidx.test.espresso.Espresso;
-import androidx.test.espresso.IdlingRegistry;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
 
@@ -50,7 +48,6 @@
 import com.android.providers.media.photopicker.data.Selection;
 import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,27 +61,27 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getMultiSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_longPress_image() {
+    public void testPreview_multiSelect_longPress_image() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // No dragBar in preview
+            onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
 
-        // No dragBar in preview
-        onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
+            // No privacy text in preview
+            onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
 
-        // No privacy text in preview
-        onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
-
-        // Verify image is previewed
-        assertMultiSelectLongPressCommonLayoutMatches();
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            // Verify image is previewed
+            assertMultiSelectLongPressCommonLayoutMatches();
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
 
         // Navigate back to Photo grid
         onView(withContentDescription("Navigate up")).perform(click());
@@ -96,39 +93,39 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_longPress_video() {
+    public void testPreview_multiSelect_longPress_video() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 3, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify video player is displayed
-        assertMultiSelectLongPressCommonLayoutMatches();
-        onView(withId(R.id.preview_player_view)).check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify video player is displayed
+            assertMultiSelectLongPressCommonLayoutMatches();
+            onView(withId(R.id.preview_player_view)).check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_longPress_select() {
+    public void testPreview_multiSelect_longPress_select() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         final int position = 1;
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, position, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
         final int selectButtonId = PREVIEW_ADD_OR_SELECT_BUTTON_ID;
-        // Select the item within Preview
-        onView(withId(selectButtonId)).perform(click());
-        // Check that button text is changed to "deselect"
-        onView(withId(selectButtonId)).check(matches(withText(R.string.deselect)));
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Select the item within Preview
+            onView(withId(selectButtonId)).perform(click());
+            // Check that button text is changed to "deselect"
+            onView(withId(selectButtonId)).check(matches(withText(R.string.deselect)));
+        }
 
         // Navigate back to PhotoGrid and check that item is selected
         onView(withContentDescription("Navigate up")).perform(click());
@@ -139,15 +136,16 @@
         // Navigate to Preview and check the select button text
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, position, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Check that button text is set to "deselect" and common layout matches
+            assertMultiSelectLongPressCommonLayoutMatches(/* isSelected */ true);
 
-        // Check that button text is set to "deselect" and common layout matches
-        assertMultiSelectLongPressCommonLayoutMatches(/* isSelected */ true);
-
-        // Click on "Deselect" and verify text changes to "Select"
-        onView(withId(selectButtonId)).perform(click());
-        // Check that button text is changed to "select"
-        onView(withId(selectButtonId)).check(matches(withText(R.string.select)));
+            // Click on "Deselect" and verify text changes to "Select"
+            onView(withId(selectButtonId)).perform(click());
+            // Check that button text is changed to "select"
+            onView(withId(selectButtonId)).check(matches(withText(R.string.select)));
+        }
 
         // Navigate back to Photo grid and verify the item is not selected
         onView(withContentDescription("Navigate up")).perform(click());
@@ -156,8 +154,7 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_longPress_showsOnlyOne() {
+    public void testPreview_multiSelect_longPress_showsOnlyOne() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select two items - first image and video item
@@ -169,66 +166,69 @@
         // Long press second image item to preview the item.
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_2_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            mRule.getScenario().onActivity(activity -> {
+                Selection selection
+                        = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
+                // Verify that we have two items(first image and video) as selected items and
+                // 1 item (second image) as item for preview
+                assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(2);
+                assertThat(selection.getSelectedItemsForPreview().size()).isEqualTo(1);
+            });
 
-        mRule.getScenario().onActivity(activity -> {
-            Selection selection
-                    = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
-            // Verify that we have two items(first image and video) as selected items and
-            // 1 item (second image) as item for preview
-            assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(2);
-            assertThat(selection.getSelectedItemsForPreview().size()).isEqualTo(1);
-        });
+            final int imageViewId = R.id.preview_imageView;
+            onView(withId(imageViewId)).check(matches(isDisplayed()));
 
-        final int imageViewId = R.id.preview_imageView;
-        onView(withId(imageViewId)).check(matches(isDisplayed()));
+            // Verify that only one item is being previewed. Swipe left and right, and verify we
+            // still have ImageView in preview.
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            onView(withId(imageViewId)).check(matches(isDisplayed()));
 
-        // Verify that only one item is being previewed. Swipe left and right, and verify we still
-        // have ImageView in preview.
-        swipeLeftAndWait();
-        onView(withId(imageViewId)).check(matches(isDisplayed()));
-
-        swipeRightAndWait();
-        onView(withId(imageViewId)).check(matches(isDisplayed()));
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            onView(withId(imageViewId)).check(matches(isDisplayed()));
+        }
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_selectButtonWidth() {
+    public void testPreview_selectButtonWidth() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-        // Check that Select button is visible
-        onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(withText(R.string.select)));
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Check that Select button is visible
+            onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(
+                    matches(withText(R.string.select)));
+        }
 
         setPortraitOrientation(mRule);
-        mRule.getScenario().onActivity(activity -> {
-            final Button addOrSelectButton
-                    = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
-            final int expectedAddOrSelectButtonWidth = activity.getResources()
-                    .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
-            // Check that button width in portrait mode = R.dimen.preview_add_or_select_width
-            assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
-        });
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            mRule.getScenario().onActivity(activity -> {
+                final Button addOrSelectButton
+                        = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
+                final int expectedAddOrSelectButtonWidth = activity.getResources()
+                        .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
+                // Check that button width in portrait mode = R.dimen.preview_add_or_select_width
+                assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
+            });
+        }
 
         setLandscapeOrientation(mRule);
-        mRule.getScenario().onActivity(activity -> {
-            final Button addOrSelectButton
-                    = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
-            final int expectedAddOrSelectButtonWidth = activity.getResources()
-                    .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
-            // Check that button width in landscape mode is = R.dimen.preview_add_or_select_width
-            assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
-        });
-    }
-
-    private void registerIdlingResourceAndWaitForIdle() {
-        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
-                new ViewPager2IdlingResource(activity.findViewById(R.id.preview_viewPager)))));
-        Espresso.onIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            mRule.getScenario().onActivity(activity -> {
+                final Button addOrSelectButton
+                        = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
+                final int expectedAddOrSelectButtonWidth = activity.getResources()
+                        .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
+                // Check that button width in landscape mode is R.dimen.preview_add_or_select_width
+                assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
+            });
+        }
     }
 
     private void assertMultiSelectLongPressCommonLayoutMatches() {
@@ -247,7 +247,7 @@
                     .check(matches(withText(R.string.select)));
         }
 
-        onView(withId(R.id.preview_select_check_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.preview_selected_check_button)).check(matches(not(isDisplayed())));
         onView(withId(R.id.preview_add_button)).check(matches(not(isDisplayed())));
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
index e4ccb76..4ad11de 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
@@ -28,7 +28,6 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.providers.media.photopicker.espresso.BottomSheetTestUtils.assertBottomSheetState;
@@ -49,7 +48,6 @@
 import android.view.View;
 
 import androidx.lifecycle.ViewModelProvider;
-import androidx.test.espresso.Espresso;
 import androidx.test.espresso.IdlingRegistry;
 import androidx.test.espresso.action.ViewActions;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
@@ -77,8 +75,7 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getMultiSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_common() {
+    public void testPreview_multiSelect_common() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
         final BottomSheetIdlingResource bottomSheetIdlingResource =
                 BottomSheetIdlingResource.register(mRule);
@@ -96,22 +93,23 @@
             clickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_2_POSITION, ICON_THUMBNAIL_ID);
             onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
 
-            registerIdlingResourceAndWaitForIdle();
+            try (ViewPager2IdlingResource idlingResource
+                         = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+                // No dragBar in preview
+                onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
 
-            // No dragBar in preview
-            onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
+                // No privacy text in preview
+                onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
+                mRule.getScenario().onActivity(activity -> {
+                    assertBottomSheetState(activity, STATE_EXPANDED);
+                });
 
-            // No privacy text in preview
-            onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
-            mRule.getScenario().onActivity(activity -> {
-                assertBottomSheetState(activity, STATE_EXPANDED);
-            });
+                assertMultiSelectPreviewCommonLayoutDisplayed();
+                onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(not(isDisplayed())));
 
-            assertMultiSelectPreviewCommonLayoutDisplayed();
-            onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(not(isDisplayed())));
-
-            // Verify ImageView is displayed
-            onView(withId(PREVIEW_IMAGE_VIEW_ID)).check(matches(isCompletelyDisplayed()));
+                // Verify ImageView is displayed
+                onView(withId(PREVIEW_IMAGE_VIEW_ID)).check(matches(isCompletelyDisplayed()));
+            }
 
             // Click back button and verify we are back to photos tab
             onView(withContentDescription("Navigate up")).perform(click());
@@ -136,7 +134,7 @@
     }
 
     @Test
-    public void testPreview_multiSelect_deselect() {
+    public void testPreview_multiSelect_deselect() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select first and second image
@@ -145,54 +143,55 @@
         // Navigate to preview
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            final String addButtonString =
+                    getTargetContext().getResources().getString(R.string.add);
+            final int previewAddButtonId = R.id.preview_add_button;
+            final int previewSelectButtonId = R.id.preview_selected_check_button;
+            final String selectedString =
+                    getTargetContext().getResources().getString(R.string.selected);
 
-        final String addButtonString =
-                getTargetContext().getResources().getString(R.string.add);
-        final int previewAddButtonId = R.id.preview_add_button;
-        final int previewSelectButtonId = R.id.preview_select_check_button;
-        final String deselectString =
-                getTargetContext().getResources().getString(R.string.deselect);
+            // Verify that, initially, we show "selected" check button
+            onView(withId(previewSelectButtonId)).check(matches(isSelected()));
+            onView(withId(previewSelectButtonId)).check(matches(withText(selectedString)));
+            // Verify that the text in Add button matches
+            onView(withId(previewAddButtonId))
+                    .check(matches(withText(addButtonString + " (2)")));
 
-        // Verify that, initially, we show deselect button
-        onView(withId(previewSelectButtonId)).check(matches(isSelected()));
-        onView(withId(previewSelectButtonId)).check(matches(withText(deselectString)));
-        // Verify that the text in Add button matches
-        onView(withId(previewAddButtonId))
-                .check(matches(withText(addButtonString + " (2)")));
+            // Deselect item in preview
+            onView(withId(previewSelectButtonId)).perform(click());
+            onView(withId(previewSelectButtonId)).check(matches(isNotSelected()));
+            onView(withId(previewSelectButtonId)).check(matches(withText(R.string.deselected)));
+            // Verify that the text in Add button now changes to "Add (1)"
+            onView(withId(previewAddButtonId))
+                    .check(matches(withText(addButtonString + " (1)")));
+            // Verify that we have one item in selected items
+            mRule.getScenario().onActivity(activity -> {
+                Selection selection
+                        = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
+                assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(1);
+            });
 
-        // Deselect item in preview
-        onView(withId(previewSelectButtonId)).perform(click());
-        onView(withId(previewSelectButtonId)).check(matches(isNotSelected()));
-        onView(withId(previewSelectButtonId)).check(matches(withText(R.string.select)));
-        // Verify that the text in Add button now changes to "Add (1)"
-        onView(withId(previewAddButtonId))
-                .check(matches(withText(addButtonString + " (1)")));
-        // Verify that we have one item in selected items
-        mRule.getScenario().onActivity(activity -> {
-            Selection selection
-                    = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
-            assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(1);
-        });
-
-        // Select the item again
-        onView(withId(previewSelectButtonId)).perform(click());
-        onView(withId(previewSelectButtonId)).check(matches(isSelected()));
-        onView(withId(previewSelectButtonId)).check(matches(withText(deselectString)));
-        // Verify that the text in Add button now changes back to "Add (2)"
-        onView(withId(previewAddButtonId))
-                .check(matches(withText(addButtonString + " (2)")));
-        // Verify that we have 2 items in selected items
-        mRule.getScenario().onActivity(activity -> {
-            Selection selection
-                    = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
-            assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(2);
-        });
+            // Select the item again
+            onView(withId(previewSelectButtonId)).perform(click());
+            onView(withId(previewSelectButtonId)).check(matches(isSelected()));
+            onView(withId(previewSelectButtonId)).check(matches(withText(selectedString)));
+            // Verify that the text in Add button now changes back to "Add (2)"
+            onView(withId(previewAddButtonId))
+                    .check(matches(withText(addButtonString + " (2)")));
+            // Verify that we have 2 items in selected items
+            mRule.getScenario().onActivity(activity -> {
+                Selection selection
+                        = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
+                assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(2);
+            });
+        }
     }
 
     @Test
     @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_navigation() {
+    public void testPreview_multiSelect_navigation() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select items
@@ -202,69 +201,69 @@
         // Navigate to preview
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Preview Order
+            // 1 - Image
+            // 2 - Image
+            // 3 - Video
+            // Navigate from Image -> Image -> Video -> Image -> Image -> Image and verify the
+            // layout matches
 
-        // Preview Order
-        // 1 - Image
-        // 2 - Image
-        // 3 - Video
-        // Navigate from Image -> Image -> Video -> Image -> Image -> Image and verify the layout
-        // matches
+            // 1. Image
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
 
-        // 1. Image
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 2. Image
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
 
-        swipeLeftAndWait();
-        // 2. Image
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 3. Video item
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            // TODO(b/197083539): We don't check the video image to be visible or not because its
+            // visibility is time sensitive. Try waiting till player is ready and assert that video
+            // image is no more visible.
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
 
-        swipeLeftAndWait();
-        // 3. Video item
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        // TODO(b/197083539): We don't check the video image to be visible or not because its
-        // visibility is time sensitive. Try waiting till player is ready and assert that video
-        // image is no more visible.
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 2. Image
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
 
-        swipeRightAndWait();
-        // 2. Image
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 1. Image
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
 
-        swipeRightAndWait();
-        // 1. Image
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
-
-        swipeLeftAndWait();
-        // 2. Image
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
-                .check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        assertSpecialFormatBadgeDoesNotExist();
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 2. Image
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            assertSpecialFormatBadgeDoesNotExist();
+        }
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_fromAlbumsTab() {
+    public void testPreview_multiSelect_fromAlbumsTab() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select 1 item in Photos tab
@@ -273,10 +272,10 @@
         assertItemSelected(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, iconCheckId);
 
         // Navigate to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
-        // The Albums tab chip is selected
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        // The Albums tab item is selected
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
         final int cameraStringId = R.string.picker_category_camera;
         // Camera album is shown
@@ -286,23 +285,23 @@
         // Navigate to preview
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
 
-        registerIdlingResourceAndWaitForIdle();
-
-        assertMultiSelectPreviewCommonLayoutDisplayed();
-        // Verify ImageView is displayed
-        onView(withId(PREVIEW_IMAGE_VIEW_ID)).check(matches(isCompletelyDisplayed()));
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            assertMultiSelectPreviewCommonLayoutDisplayed();
+            // Verify ImageView is displayed
+            onView(withId(PREVIEW_IMAGE_VIEW_ID)).check(matches(isCompletelyDisplayed()));
+        }
 
         // Click back button and verify we are back to Albums tab
         onView(withContentDescription("Navigate up")).perform(click());
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .check(matches(isSelected()));
         onView(allOf(withText(cameraStringId),
                 isDescendantOfA(withId(PICKER_TAB_RECYCLERVIEW_ID)))).check(matches(isDisplayed()));
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_viewSelectedAfterLongPress() {
+    public void testPreview_viewSelectedAfterLongPress() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select video item
@@ -312,44 +311,47 @@
         // Preview second image item using preview on long press
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_2_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify that we have one item as selected item and 1 item as item for preview, and verify
-        // they are not the same.
-        mRule.getScenario().onActivity(activity -> {
-            Selection selection
-                    = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
-            assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(1);
-            assertThat(selection.getSelectedItemsForPreview().size()).isEqualTo(1);
-            assertThat(selection.getSelectedItems().get(0))
-                    .isNotEqualTo(selection.getSelectedItemsForPreview().get(0));
-        });
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify that we have one item as selected item and 1 item as item for preview, and
+            // verify they are not the same.
+            mRule.getScenario().onActivity(activity -> {
+                Selection selection
+                        = new ViewModelProvider(activity).get(PickerViewModel.class).getSelection();
+                assertThat(selection.getSelectedItemCount().getValue()).isEqualTo(1);
+                assertThat(selection.getSelectedItemsForPreview().size()).isEqualTo(1);
+                assertThat(selection.getSelectedItems().get(0))
+                        .isNotEqualTo(selection.getSelectedItemsForPreview().get(0));
+            });
+        }
 
         // Click back button to go back to Photos tab
         onView(withContentDescription("Navigate up")).perform(click());
 
         // Navigate to preview by clicking "View Selected" button.
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
-        registerIdlingResourceAndWaitForIdle();
 
-        assertMultiSelectPreviewCommonLayoutDisplayed();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            assertMultiSelectPreviewCommonLayoutDisplayed();
 
-        // Verify that "View Selected" shows the video item, not the image item that was previewed
-        // earlier with preview on long press
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
-                .check(matches(isDisplayed()));
+            // Verify that "View Selected" shows the video item, not the image item that was
+            // previewed earlier with preview on long press
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
+                    .check(matches(isDisplayed()));
 
-        // Swipe and verify we don't preview the image item
-        swipeLeftAndWait();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
-                .check(matches(isDisplayed()));
-        swipeRightAndWait();
-        onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
-                .check(matches(isDisplayed()));
+            // Swipe and verify we don't preview the image item
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
+                    .check(matches(isDisplayed()));
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PLAYER_VIEW_ID))
+                    .check(matches(isDisplayed()));
+        }
     }
 
     @Test
-    public void testPreview_multiSelect_acrossAlbums() {
+    public void testPreview_multiSelect_acrossAlbums() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select second image and video item from Photos tab
@@ -359,7 +361,7 @@
         assertItemSelected(PICKER_TAB_RECYCLERVIEW_ID, VIDEO_POSITION, ICON_THUMBNAIL_ID);
 
         // Navigate to albums
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         final int cameraStringId = R.string.picker_category_camera;
@@ -372,30 +374,34 @@
 
         // Navigate to preview
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
-        registerIdlingResourceAndWaitForIdle();
 
-        // Deselect the image item
-        final int previewSelectButtonId = R.id.preview_select_check_button;
-        onView(withId(previewSelectButtonId)).perform(click());
+        final int previewSelectedButtonId = R.id.preview_selected_check_button;
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Deselect the image item
+            onView(withId(previewSelectedButtonId)).perform(click());
 
-        // Go back to Camera album and verify that item is deselected
-        onView(withContentDescription("Navigate up")).perform(click());
-        assertItemNotSelected(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
+            // Go back to Camera album and verify that item is deselected
+            onView(withContentDescription("Navigate up")).perform(click());
+            assertItemNotSelected(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
+        }
 
         // Go back to photo grid and verify that item is deselected
         onView(withContentDescription("Navigate up")).perform(click());
         // Navigate to Photo grid
-        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_PHOTOS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         assertItemNotSelected(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
 
         // Go back to preview and deselect another item
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
-        registerIdlingResourceAndWaitForIdle();
 
-        // Deselect the second image item
-        onView(withId(previewSelectButtonId)).perform(click());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Deselect the second image item
+            onView(withId(previewSelectedButtonId)).perform(click());
+        }
 
         // Go back to Photos tab and verify that second image item is deselected
         onView(withContentDescription("Navigate up")).perform(click());
@@ -410,8 +416,8 @@
     private void assertMultiSelectPreviewCommonLayoutDisplayed() {
         onView(withId(PREVIEW_VIEW_PAGER_ID)).check(matches(isDisplayed()));
         onView(withId(R.id.preview_add_button)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_select_check_button)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_select_check_button)).check(matches(isSelected()));
+        onView(withId(R.id.preview_selected_check_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.preview_selected_check_button)).check(matches(isSelected()));
     }
 
     private Matcher<View> ViewPagerMatcher(int viewPagerId, int itemViewId) {
@@ -434,10 +440,4 @@
             }
         };
     }
-
-    private void registerIdlingResourceAndWaitForIdle() {
-        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
-                new ViewPager2IdlingResource(activity.findViewById(PREVIEW_VIEW_PAGER_ID)))));
-        Espresso.onIdle();
-    }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
index 26570b5..2635948 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
@@ -17,7 +17,6 @@
 package com.android.providers.media.photopicker.espresso;
 
 import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.Espresso.onIdle;
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
@@ -47,14 +46,12 @@
 import android.widget.Button;
 
 import androidx.appcompat.widget.Toolbar;
-import androidx.test.espresso.Espresso;
 import androidx.test.espresso.IdlingRegistry;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
 
 import com.android.providers.media.R;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -67,8 +64,7 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_singleSelect_image() {
+    public void testPreview_singleSelect_image() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         final BottomSheetIdlingResource bottomSheetIdlingResource =
@@ -85,24 +81,24 @@
             // Navigate to preview
             longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
 
-            registerIdlingResourceAndWaitForIdle();
+            try (ViewPager2IdlingResource idlingResource
+                         = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+                // No dragBar in preview
+                bottomSheetIdlingResource.setExpectedState(STATE_EXPANDED);
+                onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
+                // No privacy text in preview
+                onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
+                mRule.getScenario().onActivity(activity -> {
+                    assertBottomSheetState(activity, STATE_EXPANDED);
+                });
 
-            // No dragBar in preview
-            bottomSheetIdlingResource.setExpectedState(STATE_EXPANDED);
-            onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
-            // No privacy text in preview
-            onView(withId(PRIVACY_TEXT_ID)).check(matches(not(isDisplayed())));
-            mRule.getScenario().onActivity(activity -> {
-                assertBottomSheetState(activity, STATE_EXPANDED);
-            });
-
-            // Verify image is previewed
-            assertSingleSelectCommonLayoutMatches();
-            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
-            // Verify no special format icon is previewed
-            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-
+                // Verify image is previewed
+                assertSingleSelectCommonLayoutMatches();
+                onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+                // Verify no special format icon is previewed
+                onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+                onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            }
             // Navigate back to Photo grid
             onView(withContentDescription("Navigate up")).perform(click());
 
@@ -121,26 +117,27 @@
     }
 
     @Test
-    public void testPreview_singleSelect_video() {
+    public void testPreview_singleSelect_video() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, VIDEO_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify video player is displayed
-        assertSingleSelectCommonLayoutMatches();
-        onView(withId(R.id.preview_player_view)).check(matches(isDisplayed()));
-        // Verify no special format icon is previewed
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify video player is displayed
+            assertSingleSelectCommonLayoutMatches();
+            onView(withId(R.id.preview_player_view)).check(matches(isDisplayed()));
+            // Verify no special format icon is previewed
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
     }
 
     @Test
-    public void testPreview_singleSelect_fromAlbumsPhoto() {
+    public void testPreview_singleSelect_fromAlbumsPhoto() throws Exception {
         // Navigate to Albums tab
-        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), withParent(withId(CHIP_CONTAINER_ID))))
+        onView(allOf(withText(PICKER_ALBUMS_STRING_ID), isDescendantOfA(withId(TAB_LAYOUT_ID))))
                 .perform(click());
 
         final int cameraStringId = R.string.picker_category_camera;
@@ -155,11 +152,12 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify image is previewed
-        assertSingleSelectCommonLayoutMatches();
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify image is previewed
+            assertSingleSelectCommonLayoutMatches();
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+        }
 
         // Navigate back to Camera album
         onView(withContentDescription("Navigate up")).perform(click());
@@ -170,27 +168,27 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_noScrimLayerAndHasSolidColorInPortrait() {
+    public void testPreview_noScrimLayerAndHasSolidColorInPortrait() throws Exception {
         setPortraitOrientation(mRule);
 
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            onView(withId(R.id.preview_top_scrim)).check(matches(not(isDisplayed())));
+            onView(withId(R.id.preview_bottom_scrim)).check(matches(not(isDisplayed())));
 
-        onView(withId(R.id.preview_top_scrim)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.preview_bottom_scrim)).check(matches(not(isDisplayed())));
-
-        mRule.getScenario().onActivity(activity -> {
-            assertBackgroundColorOnToolbarAndBottomBar(activity, R.color.preview_scrim_solid_color);
-        });
+            mRule.getScenario().onActivity(activity -> {
+                assertBackgroundColorOnToolbarAndBottomBar(activity,
+                        R.color.preview_scrim_solid_color);
+            });
+        }
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_showScrimLayerInLandscape() {
+    public void testPreview_showScrimLayerInLandscape() throws Exception {
         setLandscapeOrientation(mRule);
 
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
@@ -198,52 +196,55 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            onView(withId(R.id.preview_top_scrim)).check(matches(isDisplayed()));
+            onView(withId(R.id.preview_bottom_scrim)).check(matches(isDisplayed()));
 
-        onView(withId(R.id.preview_top_scrim)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_bottom_scrim)).check(matches(isDisplayed()));
-
-        mRule.getScenario().onActivity(activity -> {
-            assertBackgroundColorOnToolbarAndBottomBar(activity, android.R.color.transparent);
-        });
+            mRule.getScenario().onActivity(activity -> {
+                assertBackgroundColorOnToolbarAndBottomBar(activity, android.R.color.transparent);
+            });
+        }
     }
 
     @Test
-    public void testPreview_addButtonWidth() {
+    public void testPreview_addButtonWidth() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-        // Check that Add button is visible
-        onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(withText(R.string.add)));
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Check that Add button is visible
+            onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(withText(R.string.add)));
+        }
 
         setPortraitOrientation(mRule);
-        mRule.getScenario().onActivity(activity -> {
-            final Button addOrSelectButton
-                    = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
-            final int expectedAddOrSelectButtonWidth = activity.getResources()
-                    .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
-            // Check that button width in portrait mode is = R.dimen.preview_add_or_select_width
-            assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
-        });
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            mRule.getScenario().onActivity(activity -> {
+                final Button addOrSelectButton
+                        = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
+                final int expectedAddOrSelectButtonWidth = activity.getResources()
+                        .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
+                // Check that button width in portrait mode is = R.dimen.preview_add_or_select_width
+                assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
+            });
+        }
 
         setLandscapeOrientation(mRule);
-        mRule.getScenario().onActivity(activity -> {
-            final Button addOrSelectButton
-                    = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
-            final int expectedAddOrSelectButtonWidth = activity.getResources()
-                    .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
-            // Check that button width in landscape mode is == R.dimen.preview_add_or_select_width
-            assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
-        });
-    }
-
-    private void registerIdlingResourceAndWaitForIdle() {
-        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
-                new ViewPager2IdlingResource(activity.findViewById(R.id.preview_viewPager)))));
-        onIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            mRule.getScenario().onActivity(activity -> {
+                final Button addOrSelectButton
+                        = activity.findViewById(PREVIEW_ADD_OR_SELECT_BUTTON_ID);
+                final int expectedAddOrSelectButtonWidth = activity.getResources()
+                        .getDimensionPixelOffset(DIMEN_PREVIEW_ADD_OR_SELECT_WIDTH);
+                // Check that button width in landscape mode is R.dimen.preview_add_or_select_width
+                assertThat(addOrSelectButton.getWidth()).isEqualTo(expectedAddOrSelectButtonWidth);
+            });
+        }
     }
 
     private void assertBackgroundColorOnToolbarAndBottomBar(Activity activity, int colorResId) {
@@ -268,7 +269,7 @@
         // Verify that the text in Add button
         onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(withText(R.string.add)));
 
-        onView(withId(R.id.preview_select_check_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.preview_selected_check_button)).check(matches(not(isDisplayed())));
         onView(withId(R.id.preview_add_button)).check(matches(not(isDisplayed())));
     }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatMultiSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatMultiSelectTest.java
index 0119856..4a66c90 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatMultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatMultiSelectTest.java
@@ -29,8 +29,6 @@
 import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.clickItem;
 import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.longClickItem;
 
-import androidx.test.espresso.Espresso;
-import androidx.test.espresso.IdlingRegistry;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.providers.media.R;
@@ -46,77 +44,80 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getMultiSelectionIntent());
 
     @Test
-    public void testPreview_multiSelect_longPress_gif() {
+    public void testPreview_multiSelect_longPress_gif() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, GIF_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify imageView is displayed for gif preview
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify imageView is displayed for gif preview
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
-    public void testPreview_multiSelect_longPress_animatedWebp() {
+    public void testPreview_multiSelect_longPress_animatedWebp() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, ANIMATED_WEBP_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify imageView is displayed for animated webp preview
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
 
-        // Verify imageView is displayed for animated webp preview
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+            // Verify GIF icon is shown for animated webp preview
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
 
-        // Verify GIF icon is shown for animated webp preview
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-
-        // Verify Motion Photo icon is not shown for animated webp preview
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            // Verify Motion Photo icon is not shown for animated webp preview
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
-    public void testPreview_multiSelect_longPress_nonAnimatedWebp() {
+    public void testPreview_multiSelect_longPress_nonAnimatedWebp() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, NON_ANIMATED_WEBP_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify imageView is displayed for non-animated webp preview
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
 
-        // Verify imageView is displayed for non-animated webp preview
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+            // Verify GIF icon is not shown for non-animated webp preview
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
 
-        // Verify GIF icon is not shown for non-animated webp preview
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-
-        // Verify Motion Photo icon is not shown for non-animated webp preview
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            // Verify Motion Photo icon is not shown for non-animated webp preview
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_longPress_motionPhoto() {
+    public void testPreview_multiSelect_longPress_motionPhoto() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, MOTION_PHOTO_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify imageView is displayed for motion photo preview
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify imageView is displayed for motion photo preview
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
+            onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
     }
 
     @Test
     @Ignore("Enable after b/218806007 is fixed")
-    public void testPreview_multiSelect_navigation() {
+    public void testPreview_multiSelect_navigation() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Select items
@@ -128,67 +129,62 @@
         // Navigate to preview
         onView(withId(VIEW_SELECTED_BUTTON_ID)).perform(click());
 
-        registerIdlingResourceAndWaitForIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Preview Order
+            // 1 - Image
+            // 2 - Gif
+            // 3 - Animated Webp
+            // 4 - MotionPhoto
+            // 5 - Non-Animated Webp
+            // Navigate from Image -> Gif -> Motion Photo -> Animated Webp -> Non-Animated Webp ->
+            // Animated Webp-> Gif -> Image and verify the layout
+            // matches. This test does not check for common layout as that is already covered in
+            // other tests.
 
-        // Preview Order
-        // 1 - Image
-        // 2 - Gif
-        // 3 - Animated Webp
-        // 4 - MotionPhoto
-        // 5 - Non-Animated Webp
-        // Navigate from Image -> Gif -> Motion Photo -> Animated Webp -> Non-Animated Webp ->
-        // Animated Webp-> Gif -> Image and verify the layout
-        // matches. This test does not check for common layout as that is already covered in
-        // other tests.
+            // 1. Image
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
 
-        // 1. Image
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 2. Gif
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
 
-        swipeLeftAndWait();
-        // 2. Gif
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 3. Animated Webp
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
 
-        swipeLeftAndWait();
-        // 3. Animated Webp
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 4. Motion Photo
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
 
-        swipeLeftAndWait();
-        // 4. Motion Photo
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 5. Non-Animated Webp
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
 
-        swipeLeftAndWait();
-        // 5. Non-Animated Webp
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 4. Motion Photo
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
 
-        swipeRightAndWait();
-        // 4. Motion Photo
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 3. Animated Webp
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
 
-        swipeRightAndWait();
-        // 3. Animated Webp
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 2. Gif
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
 
-        swipeRightAndWait();
-        // 2. Gif
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-
-        swipeRightAndWait();
-        // 1. Image
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-    }
-
-    private void registerIdlingResourceAndWaitForIdle() {
-        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
-                new ViewPager2IdlingResource(activity.findViewById(R.id.preview_viewPager)))));
-        Espresso.onIdle();
+            swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
+            // 1. Image
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
     }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatSingleSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatSingleSelectTest.java
index a2fc2c1..9fcd9e8 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatSingleSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/SpecialFormatSingleSelectTest.java
@@ -33,14 +33,12 @@
 
 import static org.hamcrest.Matchers.not;
 
-import androidx.test.espresso.Espresso;
 import androidx.test.espresso.IdlingRegistry;
 import androidx.test.espresso.action.ViewActions;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.providers.media.R;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -51,7 +49,6 @@
             = new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testPhotoGridLayout_motionPhoto() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
@@ -68,7 +65,6 @@
     }
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testPhotoGridLayout_gif() throws Exception {
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
@@ -98,7 +94,6 @@
         assertSingleSelectImageThumbnailCommonLayout(ANIMATED_WEBP_POSITION);
         assertItemNotDisplayed(PICKER_TAB_RECYCLERVIEW_ID, ANIMATED_WEBP_POSITION,
                 ICON_MOTION_PHOTO_ID);
-
     }
 
     @Test
@@ -130,12 +125,13 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, GIF_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify gif icon is displayed for gif preview
-        assertSingleSelectImagePreviewCommonLayout();
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify gif icon is displayed for gif preview
+            assertSingleSelectImagePreviewCommonLayout();
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
@@ -147,12 +143,13 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, ANIMATED_WEBP_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify gif icon is displayed for animated preview
-        assertSingleSelectImagePreviewCommonLayout();
-        onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify gif icon is displayed for animated preview
+            assertSingleSelectImagePreviewCommonLayout();
+            onView(withId(PREVIEW_GIF_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
@@ -164,12 +161,13 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, NON_ANIMATED_WEBP_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify gif icon is not displayed for non-animated webp preview
-        assertSingleSelectImagePreviewCommonLayout();
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify gif icon is not displayed for non-animated webp preview
+            assertSingleSelectImagePreviewCommonLayout();
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(doesNotExist());
+        }
     }
 
     @Test
@@ -181,18 +179,13 @@
         // Navigate to preview
         longClickItem(PICKER_TAB_RECYCLERVIEW_ID, MOTION_PHOTO_POSITION, ICON_THUMBNAIL_ID);
 
-        registerIdlingResourceAndWaitForIdle();
-
-        // Verify motion photo icon is displayed for motion photo preview
-        assertSingleSelectImagePreviewCommonLayout();
-        onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
-        onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
-    }
-
-    private void registerIdlingResourceAndWaitForIdle() {
-        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
-                new ViewPager2IdlingResource(activity.findViewById(R.id.preview_viewPager)))));
-        Espresso.onIdle();
+        try (ViewPager2IdlingResource idlingResource
+                     = ViewPager2IdlingResource.register(mRule, PREVIEW_VIEW_PAGER_ID)) {
+            // Verify motion photo icon is displayed for motion photo preview
+            assertSingleSelectImagePreviewCommonLayout();
+            onView(withId(PREVIEW_MOTION_PHOTO_ID)).check(matches(isDisplayed()));
+            onView(withId(PREVIEW_GIF_ID)).check(doesNotExist());
+        }
     }
 
     private void assertSingleSelectCommonLayoutMatches() {
@@ -201,7 +194,7 @@
         // Verify that the text in Add button
         onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(withText(R.string.add)));
 
-        onView(withId(R.id.preview_select_check_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.preview_selected_check_button)).check(matches(not(isDisplayed())));
         onView(withId(R.id.preview_add_button)).check(matches(not(isDisplayed())));
     }
 
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/ViewPager2IdlingResource.java b/tests/src/com/android/providers/media/photopicker/espresso/ViewPager2IdlingResource.java
index 091327c..1063561 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/ViewPager2IdlingResource.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/ViewPager2IdlingResource.java
@@ -16,14 +16,16 @@
 
 package com.android.providers.media.photopicker.espresso;
 
+import androidx.test.espresso.IdlingRegistry;
 import androidx.test.espresso.IdlingResource;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.viewpager2.widget.ViewPager2;
 
 /**
  * An {@link IdlingResource} waiting for the {@link ViewPager2} swipe to enter
  * {@link ViewPager2#SCROLL_STATE_IDLE} state.
  */
-public class ViewPager2IdlingResource implements IdlingResource {
+public class ViewPager2IdlingResource implements IdlingResource, AutoCloseable {
     private final ViewPager2 mViewPager;
     private ResourceCallback mResourceCallback;
 
@@ -47,6 +49,11 @@
         mResourceCallback = callback;
     }
 
+    @Override
+    public void close() throws Exception {
+        IdlingRegistry.getInstance().register(this);
+    }
+
     private final class IdleStateListener extends ViewPager2.OnPageChangeCallback {
         @Override
         public void onPageScrollStateChanged(int state) {
@@ -55,4 +62,19 @@
             }
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * @return {@link ViewPager2IdlingResource} that is registered to the activity
+     * related to the given {@link ActivityScenarioRule} and the resource ID of the ViewPager2.
+     */
+    public static ViewPager2IdlingResource register(
+            ActivityScenarioRule<PhotoPickerTestActivity> rule, int viewPager2Id) {
+        final ViewPager2IdlingResource[] idlingResources = new ViewPager2IdlingResource[1];
+        rule.getScenario().onActivity((activity -> {
+            idlingResources[0] = new ViewPager2IdlingResource(
+                    activity.findViewById(viewPager2Id));
+        }));
+        IdlingRegistry.getInstance().register(idlingResources[0]);
+        return idlingResources[0];
+    }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java b/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
index b804286..59ecfc7 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
@@ -56,7 +56,6 @@
             new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
 
     @Test
-    @Ignore("Enable after b/218806007 is fixed")
     public void testProfileButton_dialog() throws Exception {
         // Register bottom sheet idling resource so that we don't read bottom sheet state when
         // in between changing states
diff --git a/tests/src/com/android/providers/media/photopicker/util/DateTimeUtilsTest.java b/tests/src/com/android/providers/media/photopicker/util/DateTimeUtilsTest.java
index e534cfd..2c29188 100644
--- a/tests/src/com/android/providers/media/photopicker/util/DateTimeUtilsTest.java
+++ b/tests/src/com/android/providers/media/photopicker/util/DateTimeUtilsTest.java
@@ -18,18 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.Locale;
 
 @RunWith(AndroidJUnit4.class)
@@ -41,50 +38,50 @@
             FAKE_DATE.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
 
     @Test
-    public void testGetDateTimeString_today() throws Exception {
+    public void testGetDateHeaderString_today() throws Exception {
         final long when = generateDateTimeMillis(FAKE_DATE);
 
-        final String result = DateTimeUtils.getDateTimeString(when, FAKE_DATE);
+        String result = DateTimeUtils.getDateHeaderString(when, FAKE_DATE);
 
         assertThat(result).isEqualTo(DateTimeUtils.getTodayString());
     }
 
     @Test
-    public void testGetDateTimeString_yesterday() throws Exception {
+    public void testGetDateHeaderString_yesterday() throws Exception {
         final LocalDate whenDate = FAKE_DATE.minusDays(1);
         final long when = generateDateTimeMillis(whenDate);
 
-        final String result = DateTimeUtils.getDateTimeString(when, FAKE_DATE);
+        final String result = DateTimeUtils.getDateHeaderString(when, FAKE_DATE);
 
         assertThat(result).isEqualTo(DateTimeUtils.getYesterdayString());
     }
 
     @Test
-    public void testGetDateTimeString_weekday() throws Exception {
+    public void testGetDateHeaderString_weekday() throws Exception {
         final LocalDate whenDate = FAKE_DATE.minusDays(3);
         final long when = generateDateTimeMillis(whenDate);
 
-        final String result = DateTimeUtils.getDateTimeString(when, FAKE_DATE);
+        final String result = DateTimeUtils.getDateHeaderString(when, FAKE_DATE);
 
         assertThat(result).isEqualTo("Saturday");
     }
 
     @Test
-    public void testGetDateTimeString_weekdayAndDate() throws Exception {
+    public void testGetDateHeaderString_weekdayAndDate() throws Exception {
         final LocalDate whenDate = FAKE_DATE.minusMonths(1);
         final long when = generateDateTimeMillis(whenDate);
 
-        final String result = DateTimeUtils.getDateTimeString(when, FAKE_DATE);
+        final String result = DateTimeUtils.getDateHeaderString(when, FAKE_DATE);
 
         assertThat(result).isEqualTo("Sun, Jun 7");
     }
 
     @Test
-    public void testGetDateTimeString_weekdayDateAndYear() throws Exception {
+    public void testGetDateHeaderString_weekdayDateAndYear() throws Exception {
         final LocalDate whenDate = FAKE_DATE.minusYears(1);
         long when = generateDateTimeMillis(whenDate);
 
-        final String result = DateTimeUtils.getDateTimeString(when, FAKE_DATE);
+        final String result = DateTimeUtils.getDateHeaderString(when, FAKE_DATE);
 
         assertThat(result).isEqualTo("Sun, Jul 7, 2019");
     }
@@ -120,6 +117,45 @@
     }
 
     @Test
+    public void testGetDateTimeStringForContentDesc() throws Exception {
+        final long when = generateDateTimeMillis(FAKE_DATE);
+
+        String result = DateTimeUtils.getDateTimeStringForContentDesc(when);
+
+        assertThat(result).isEqualTo("Jul 7, 2020, 12:00:00 AM");
+    }
+
+    @Test
+    public void testGetDateTimeStringForContentDesc_time() throws Exception {
+        long when = generateDateTimeMillisAt(
+                FAKE_DATE, /* hour */ 10, /* minute */ 10, /* second */ 10);
+
+        final String result = DateTimeUtils.getDateTimeStringForContentDesc(when);
+
+        assertThat(result).isEqualTo("Jul 7, 2020, 10:10:10 AM");
+    }
+
+    @Test
+    public void testGetDateTimeStringForContentDesc_singleDigitHour() throws Exception {
+        long when = generateDateTimeMillisAt(
+                FAKE_DATE, /* hour */ 1, /* minute */ 0, /* second */ 0);
+
+        final String result = DateTimeUtils.getDateTimeStringForContentDesc(when);
+
+        assertThat(result).isEqualTo("Jul 7, 2020, 1:00:00 AM");
+    }
+
+    @Test
+    public void testGetDateTimeStringForContentDesc_timePM() throws Exception {
+        long when = generateDateTimeMillisAt(
+                FAKE_DATE, /* hour */ 22, /* minute */ 0, /* second */ 0);
+
+        final String result = DateTimeUtils.getDateTimeStringForContentDesc(when);
+
+        assertThat(result).isEqualTo("Jul 7, 2020, 10:00:00 PM");
+    }
+
+    @Test
     public void testIsSameDay_differentYear_false() throws Exception {
         final LocalDate whenDate = FAKE_DATE.minusYears(1);
         long when = generateDateTimeMillis(whenDate);
@@ -151,4 +187,9 @@
     private static long generateDateTimeMillis(LocalDate when) {
         return when.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
     }
+
+    private long generateDateTimeMillisAt(LocalDate when, int hour, int minute, int second) {
+        return ZonedDateTime.of(when.atTime(hour, minute, second), ZoneId.systemDefault())
+                .toInstant().toEpochMilli();
+    }
 }