Merge "plurals cleanup in android/packages/providers/MediaProvider" into sc-mainline-prod
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index 187da31..0a53fb5 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -8,7 +8,6 @@
     method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
     method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
     method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
-    method @Nullable public static String getCloudProvider(@NonNull android.content.ContentResolver);
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
     method public static long getGeneration(@NonNull android.content.Context, @NonNull String);
@@ -23,7 +22,6 @@
     method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
     method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
     method public static boolean isCurrentSystemGallery(@NonNull android.content.ContentResolver, int, @NonNull String);
-    method public static boolean notifyCloudEvent(@NonNull android.content.ContentResolver);
     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/api/module-lib-current.txt b/apex/framework/api/module-lib-current.txt
index eae8f7a..d802177 100644
--- a/apex/framework/api/module-lib-current.txt
+++ b/apex/framework/api/module-lib-current.txt
@@ -1,41 +1 @@
 // Signature format: 2.0
-package android.provider {
-
-  public final class CloudMediaProviderContract {
-    field public static final String EXTRA_FILTER_ALBUM = "android.provider.extra.FILTER_ALBUM";
-    field public static final String EXTRA_FILTER_MIMETYPE = "android.provider.extra.FILTER_MIMETYPE";
-    field public static final String EXTRA_FILTER_SIZE_BYTES = "android.provider.extra.FILTER_SIZE_BYTES";
-    field public static final String EXTRA_GENERATION = "android.provider.extra.GENERATION";
-    field public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
-    field public static final String MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS";
-    field public static final String METHOD_GET_MEDIA_INFO = "android:getMediaInfo";
-    field public static final String PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER";
-  }
-
-  public static final class CloudMediaProviderContract.AlbumColumns {
-    field public static final String DATE_TAKEN_MS = "date_taken_ms";
-    field public static final String DISPLAY_NAME = "display_name";
-    field public static final String ID = "id";
-    field public static final String MEDIA_COUNT = "album_media_count";
-    field public static final String MEDIA_COVER_ID = "album_media_cover_id";
-  }
-
-  public static final class CloudMediaProviderContract.MediaColumns {
-    field public static final String DATE_TAKEN_MS = "date_taken_ms";
-    field public static final String DURATION_MS = "duration_ms";
-    field public static final String GENERATION_MODIFIED = "generation_modified";
-    field public static final String ID = "id";
-    field public static final String IS_FAVORITE = "is_favorite";
-    field public static final String MEDIA_STORE_URI = "media_store_uri";
-    field public static final String MIME_TYPE = "mime_type";
-    field public static final String SIZE_BYTES = "size_bytes";
-  }
-
-  public static final class CloudMediaProviderContract.MediaInfo {
-    field public static final String MEDIA_COUNT = "media_count";
-    field public static final String MEDIA_GENERATION = "media_generation";
-    field public static final String MEDIA_VERSION = "media_version";
-  }
-
-}
-
diff --git a/apex/framework/java/android/provider/CloudMediaProvider.java b/apex/framework/java/android/provider/CloudMediaProvider.java
index 3ef174f..ae96fb1 100644
--- a/apex/framework/java/android/provider/CloudMediaProvider.java
+++ b/apex/framework/java/android/provider/CloudMediaProvider.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import static android.provider.CloudMediaProviderContract.METHOD_GET_ACCOUNT_INFO;
 import static android.provider.CloudMediaProviderContract.METHOD_GET_MEDIA_INFO;
 import static android.provider.CloudMediaProviderContract.URI_PATH_ALBUM;
 import static android.provider.CloudMediaProviderContract.URI_PATH_DELETED_MEDIA;
@@ -116,6 +117,25 @@
     }
 
     /**
+     * Returns account related information for the media collection.
+     * <p>
+     * This is useful for the OS to populate a settings page with account information and allow
+     * users configure their media collection account.
+     *
+     * @param extras containing keys to filter result:
+     * <ul>
+     * <li> {@link CloudMediaProviderContract.AccountInfo#ACTIVE_ACCOUNT_NAME}
+     * <li> {@link CloudMediaProviderContract.AccountInfo#ACCOUNT_CONFIGURATION_INTENT}
+     * </ul>
+     *
+     * @return {@link Bundle} containing {@link CloudMediaProviderContract.AccountInfo}
+     */
+    @NonNull
+    public Bundle onGetAccountInfo(@Nullable Bundle extras) {
+        throw new UnsupportedOperationException("getAccountInfo not supported");
+    }
+
+        /**
      * Returns metadata about the media collection itself.
      * <p>
      * This is useful for the OS to determine if its cache of media items in the collection is
@@ -281,6 +301,8 @@
             throws FileNotFoundException {
         if (METHOD_GET_MEDIA_INFO.equals(method)) {
             return onGetMediaInfo(extras);
+        } else if (METHOD_GET_ACCOUNT_INFO.equals(method)) {
+            return onGetAccountInfo(extras);
         } else {
             throw new UnsupportedOperationException("Method not supported " + method);
         }
diff --git a/apex/framework/java/android/provider/CloudMediaProviderContract.java b/apex/framework/java/android/provider/CloudMediaProviderContract.java
index f8a2811..840f009 100644
--- a/apex/framework/java/android/provider/CloudMediaProviderContract.java
+++ b/apex/framework/java/android/provider/CloudMediaProviderContract.java
@@ -17,6 +17,7 @@
 package android.provider;
 
 import android.annotation.SystemApi;
+import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.database.Cursor;
@@ -34,7 +35,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class CloudMediaProviderContract {
     private static final String TAG = "CloudMediaProviderContract";
 
@@ -325,6 +325,30 @@
         public static final String MEDIA_COUNT = "media_count";
     }
 
+    /** Constants related to the account information */
+    public static final class AccountInfo {
+        private AccountInfo() {}
+
+        /**
+         * Name of the account owning the media collection synced from the cloud provider.
+         * <p>
+         * Type: STRING
+         *
+         * @see CloudMediaProvider#onGetAccountInfo
+         */
+        public static final String ACTIVE_ACCOUNT_NAME = "active_account_name";
+
+        /**
+         * {@link Intent} Intent to launch an {@link Activity} to allow users configure their media
+         * collection account information like the active account.
+         * <p>
+         * Type: PARCELABLE
+         *
+         * @see CloudMediaProvider#onGetAccountInfo
+         */
+        public static final String ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent";
+    }
+
     /**
      * Opaque pagination token to retrieve the next page (cursor) from a media or album query.
      * <p>
@@ -433,10 +457,17 @@
      *
      * {@hide}
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String METHOD_GET_MEDIA_INFO = "android:getMediaInfo";
 
     /**
+     * Constant used to execute {@link CloudMediaProvider#onGetAccountInfo} via
+     * {@link ContentProvider#call}.
+     *
+     * {@hide}
+     */
+    public static final String METHOD_GET_ACCOUNT_INFO = "android:getAccountInfo";
+
+    /**
      * URI path for {@link CloudMediaProvider#onQueryMedia}
      *
      * {@hide}
@@ -470,4 +501,11 @@
      * {@hide}
      */
     public static final String URI_PATH_MEDIA_INFO = "media_info";
+
+    /**
+     * URI path for {@link CloudMediaProvider#onGetAccountInfo}
+     *
+     * {@hide}
+     */
+    public static final String URI_PATH_ACCOUNT_INFO = "account_info";
 }
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 8a8a811..39df119 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -4579,6 +4579,8 @@
      * enabled.
      *
      * See android.provider.CloudMediaProvider
+     *
+     * @hide
      */
     // TODO(b/202733511): Convert See to @see tag after CloudMediaProvider API is unhidden
     @Nullable
@@ -4600,6 +4602,8 @@
      * {@link #getCloudProvider(ContentResolver)}, the request will be unsuccessful.
      *
      * @return {@code true} if the notification was successful, {@code false} otherwise
+     *
+     * @hide
      */
     public static boolean notifyCloudEvent(@NonNull ContentResolver resolver) {
         Objects.requireNonNull(resolver);
diff --git a/res/layout/item_album_grid.xml b/res/layout/item_album_grid.xml
index 8c148c1..ed6578a 100644
--- a/res/layout/item_album_grid.xml
+++ b/res/layout/item_album_grid.xml
@@ -42,14 +42,16 @@
     <TextView
         android:id="@+id/album_name"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/picker_album_name_min_height"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/picker_album_name_min_height"
         android:layout_marginTop="@dimen/picker_album_name_margin"
         android:textAppearance="?attr/textAppearanceSubtitle2"/>
 
     <TextView
         android:id="@+id/item_count"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/picker_album_item_count_height"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/picker_album_item_count_height"
         android:layout_marginTop="@dimen/picker_album_item_count_margin"
         android:textAppearance="?attr/textAppearanceCaption"/>
 
diff --git a/res/layout/item_date_header.xml b/res/layout/item_date_header.xml
index fd6f826..22c854d 100644
--- a/res/layout/item_date_header.xml
+++ b/res/layout/item_date_header.xml
@@ -17,6 +17,7 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
           android:id="@+id/date_header_title"
           android:layout_width="match_parent"
-          android:layout_height="@dimen/picker_date_header_height"
+          android:layout_height="wrap_content"
+          android:minHeight="@dimen/picker_date_header_height"
           android:padding="@dimen/picker_date_header_padding"
           android:textAppearance="@style/PickerDateHeader"/>
\ No newline at end of file
diff --git a/src/com/android/providers/media/PickerUriResolver.java b/src/com/android/providers/media/PickerUriResolver.java
index 1b4d9fb..d39071d 100644
--- a/src/com/android/providers/media/PickerUriResolver.java
+++ b/src/com/android/providers/media/PickerUriResolver.java
@@ -167,6 +167,11 @@
                 + CloudMediaProviderContract.URI_PATH_MEDIA_INFO);
     }
 
+    public static Uri getAccountInfoUri(String authority) {
+        return Uri.parse("content://" + authority + "/"
+                + CloudMediaProviderContract.URI_PATH_ACCOUNT_INFO);
+    }
+
     public static Uri getAlbumUri(String authority) {
         return Uri.parse("content://" + authority + "/"
                 + CloudMediaProviderContract.URI_PATH_ALBUM);
diff --git a/src/com/android/providers/media/photopicker/PickerDataLayer.java b/src/com/android/providers/media/photopicker/PickerDataLayer.java
index 11d9338..b343c81 100644
--- a/src/com/android/providers/media/photopicker/PickerDataLayer.java
+++ b/src/com/android/providers/media/photopicker/PickerDataLayer.java
@@ -17,8 +17,10 @@
 package com.android.providers.media.photopicker;
 
 import static android.provider.CloudMediaProviderContract.EXTRA_GENERATION;
+import static android.provider.CloudMediaProviderContract.METHOD_GET_ACCOUNT_INFO;
 import static android.provider.CloudMediaProviderContract.MediaColumns;
 import static android.provider.CloudMediaProviderContract.MediaInfo;
+import static com.android.providers.media.PickerUriResolver.getAccountInfoUri;
 import static com.android.providers.media.PickerUriResolver.getAlbumUri;
 import static com.android.providers.media.PickerUriResolver.getMediaUri;
 import static com.android.providers.media.PickerUriResolver.getDeletedMediaUri;
@@ -28,11 +30,13 @@
 import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT;
 
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MergeCursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.CloudMediaProviderContract;
 import android.provider.CloudMediaProviderContract.AlbumColumns;
 import android.provider.MediaStore;
 import android.util.Log;
@@ -118,6 +122,32 @@
         return mergeCursor;
     }
 
+    public AccountInfo fetchCloudAccountInfo() {
+        final String cloudProvider = mDbFacade.getCloudProvider();
+        if (cloudProvider == null) {
+            return null;
+        }
+
+        try {
+            final Bundle accountBundle = mContext.getContentResolver().call(
+                    getAccountInfoUri(cloudProvider), METHOD_GET_ACCOUNT_INFO, /* arg */ null,
+                    /* extras */ null);
+            final String accountName = accountBundle.getString(
+                    CloudMediaProviderContract.AccountInfo.ACTIVE_ACCOUNT_NAME);
+            final Intent configIntent = (Intent) accountBundle.getParcelable(
+                    CloudMediaProviderContract.AccountInfo.ACCOUNT_CONFIGURATION_INTENT);
+
+            if (accountName == null) {
+                return null;
+            }
+
+            return new AccountInfo(accountName, configIntent);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to fetch account info from cloud provider: " + cloudProvider, e);
+            return null;
+        }
+    }
+
     private Cursor queryProviderAlbums(String authority, Bundle queryArgs) {
         if (authority == null) {
             // Can happen if there is no cloud provider
@@ -152,4 +182,14 @@
         // Cloud provider has switched since last query, so no longer valid
         return null;
     }
+
+    public static class AccountInfo {
+        public final String accountName;
+        public final Intent accountConfigurationIntent;
+
+        public AccountInfo(String accountName, Intent accountConfigurationIntent) {
+            this.accountName = accountName;
+            this.accountConfigurationIntent = accountConfigurationIntent;
+        }
+    }
 }
diff --git a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
index 60fedab..909f28c 100644
--- a/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
+++ b/tests/src/com/android/providers/media/PickerProviderMediaGenerator.java
@@ -16,13 +16,16 @@
 
 package com.android.providers.media;
 
+import static android.provider.CloudMediaProviderContract.AccountInfo;
 import static android.provider.CloudMediaProviderContract.AlbumColumns;
 import static android.provider.CloudMediaProviderContract.MediaColumns;
 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 android.content.Intent;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.provider.CloudMediaProvider;
 
@@ -73,6 +76,8 @@
         private final List<TestAlbum> mAlbums = new ArrayList<>();
         private String mVersion;
         private long mGeneration;
+        private String mAccountName;
+        private Intent mAccountConfigurationIntent;
 
         public Cursor getMedia(long generation, String albumdId, String mimeType, long sizeBytes) {
             return getCursor(mMedia, generation, albumdId, mimeType, sizeBytes,
@@ -89,6 +94,20 @@
                     /* isDeleted */ true);
         }
 
+        public Bundle getAccountInfo() {
+            Bundle bundle = new Bundle();
+            bundle.putString(AccountInfo.ACTIVE_ACCOUNT_NAME, mAccountName);
+            bundle.putParcelable(AccountInfo.ACCOUNT_CONFIGURATION_INTENT,
+                    mAccountConfigurationIntent);
+
+            return bundle;
+        }
+
+        public void setAccountInfo(String accountName, Intent configIntent) {
+            mAccountName = accountName;
+            mAccountConfigurationIntent = configIntent;
+        }
+
         public void addMedia(String localId, String cloudId) {
             mDeletedMedia.remove(createPlaceholderMedia(localId, cloudId));
             mMedia.add(0, createTestMedia(localId, cloudId));
diff --git a/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java b/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java
index 93ff18a..7d661e1 100644
--- a/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java
+++ b/tests/src/com/android/providers/media/cloudproviders/CloudProviderPrimary.java
@@ -101,4 +101,9 @@
 
         return bundle;
     }
+
+    @Override
+    public Bundle onGetAccountInfo(Bundle extras) {
+        return mMediaGenerator.getAccountInfo();
+    }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
index b003c70..a0e69fc 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
@@ -29,6 +29,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -518,6 +519,29 @@
         }
     }
 
+    @Test
+    public void testFetchCloudAccountInfo() {
+        // Cloud provider is not set so cloud account info is null
+        assertThat(mDataLayer.fetchCloudAccountInfo()).isNull();
+
+        // Set cloud provider
+        mFacade.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+
+        // Still null since cloud provider doesn't return account info yet
+        assertThat(mDataLayer.fetchCloudAccountInfo()).isNull();
+
+        // Fake cloud provider cloud account info
+        final String expectedName = "bar";
+        final Intent expectedIntent = new Intent("foo");
+        mCloudPrimaryMediaGenerator.setAccountInfo(expectedName, expectedIntent);
+
+        // Verify account info
+        final PickerDataLayer.AccountInfo info = mDataLayer.fetchCloudAccountInfo();
+        assertThat(info).isNotNull();
+        assertThat(info.accountName).isEqualTo(expectedName);
+        assertThat(info.accountConfigurationIntent).isEqualTo(expectedIntent);
+    }
+
     private static void waitForIdle() {
         final CountDownLatch latch = new CountDownLatch(1);
         BackgroundThread.getExecutor().execute(() -> {
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 bb97c4d..14a4881 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerBaseTest.java
@@ -176,9 +176,9 @@
         // Create files and change dateModified so that we can predict the recyclerView item
         // position. Set modified date ahead of time, so that even if other files are created,
         // the below files always have positions 1, 2 and 3.
-        createFile(IMAGE_1_FILE, timeNow + 3000);
-        createFile(IMAGE_2_FILE, timeNow + 2000);
-        createFile(VIDEO_FILE, timeNow + 1000);
+        createFile(IMAGE_1_FILE, timeNow + 30000);
+        createFile(IMAGE_2_FILE, timeNow + 20000);
+        createFile(VIDEO_FILE, timeNow + 10000);
     }
 
     private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)