Add cloud provider APIs to MediaStore

1. MediaStore#getCloudProvider
2. MediaStore#notifyCloudEvent

Test: m
Bug: 195008799
CTS-Coverage-Bug: 193668830
Change-Id: I5f44d2dee87335cea773bcd88222c626a109f9d0
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index 9d1fd44..6ac4dc1 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -68,6 +68,7 @@
     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);
@@ -83,6 +84,7 @@
     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/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 00589e8..848ddbf 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -243,11 +243,17 @@
     public static final String EXTRA_IS_SYSTEM_GALLERY_RESPONSE = "is_system_gallery_response";
 
     /** {@hide} */
+    public static final String GET_CLOUD_PROVIDER_CALL = "get_cloud_provider";
+    /** {@hide} */
+    public static final String NOTIFY_CLOUD_EVENT_CALL = "notify_cloud_event";
+    /** {@hide} */
     public static final String SYNC_PROVIDERS_CALL = "sync_providers";
     /** {@hide} */
     public static final String SET_CLOUD_PROVIDER_CALL = "set_cloud_provider";
     /** {@hide} */
     public static final String EXTRA_CLOUD_PROVIDER = "cloud_provider";
+    /** {@hide} */
+    public static final String EXTRA_NOTIFY_CLOUD_EVENT_RESULT = "notify_cloud_event_result";
 
     /** {@hide} */
     public static final String QUERY_ARG_LIMIT = ContentResolver.QUERY_ARG_LIMIT;
@@ -4561,4 +4567,38 @@
                 return false;
         }
     }
+
+    /**
+     * Returns the authority of the currently enabled cloud provider or {@code null} if there's none
+     * enabled.
+     *
+     * See android.provider.CloudMediaProvider
+     */
+    // TODO(b/202733511): Convert See to @see tag after CloudMediaProvider API is unhidden
+    @Nullable
+    public static String getCloudProvider(@NonNull ContentResolver resolver) {
+        Objects.requireNonNull(resolver);
+
+        final Bundle out = resolver.call(AUTHORITY, GET_CLOUD_PROVIDER_CALL, null, null);
+        return out.getString(EXTRA_CLOUD_PROVIDER);
+    }
+
+    /**
+     * Notifies the OS about a cloud event requiring a full or incremental media collection sync
+     * for the currently enabled cloud provider.
+     *
+     * The OS will schedule the sync in the background and will attempt to batch frequent
+     * notifications into a single sync event.
+     *
+     * If the caller is not the currently enabled cloud provider as returned by
+     * {@link #getCloudProvider(ContentResolver)}, the request will be unsuccessful.
+     *
+     * @return {@code true} if the notification was successful, {@code false} otherwise
+     */
+    public static boolean notifyCloudEvent(@NonNull ContentResolver resolver) {
+        Objects.requireNonNull(resolver);
+
+        final Bundle out = resolver.call(AUTHORITY, NOTIFY_CLOUD_EVENT_CALL, null, null);
+        return out.getBoolean(EXTRA_NOTIFY_CLOUD_EVENT_RESULT);
+    }
 }
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 90bfdbc..8fb7007 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -6027,15 +6027,38 @@
                 } finally {
                     restoreLocalCallingIdentity(token);
                 }
-            case MediaStore.SET_CLOUD_PROVIDER_CALL:
+            case MediaStore.SET_CLOUD_PROVIDER_CALL: {
                 // TODO(b/190713331): Remove after initial development
                 final String cloudProvider = extras.getString(MediaStore.EXTRA_CLOUD_PROVIDER);
                 Log.i(TAG, "Developer initiated cloud provider switch: " + cloudProvider);
                 mPickerSyncController.setCloudProvider(cloudProvider);
                 // fall through
-            case MediaStore.SYNC_PROVIDERS_CALL:
+            }
+            case MediaStore.SYNC_PROVIDERS_CALL: {
                 syncPicker();
                 return new Bundle();
+            }
+            case MediaStore.GET_CLOUD_PROVIDER_CALL: {
+                final String cloudProvider = mPickerSyncController.getCloudProvider();
+
+                Bundle bundle = new Bundle();
+                bundle.putString(MediaStore.EXTRA_CLOUD_PROVIDER, cloudProvider);
+                return bundle;
+            }
+            case MediaStore.NOTIFY_CLOUD_EVENT_CALL: {
+                final boolean notifyCloudEventResult;
+                if (mPickerSyncController.isProviderEnabled(Binder.getCallingUid())) {
+                    mPickerSyncController.notifyMediaEvent();
+                    notifyCloudEventResult = true;
+                } else {
+                    notifyCloudEventResult = false;
+                }
+
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(MediaStore.EXTRA_NOTIFY_CLOUD_EVENT_RESULT,
+                        notifyCloudEventResult);
+                return bundle;
+            }
             default:
                 throw new UnsupportedOperationException("Unsupported call: " + method);
         }