Merge "Revert "Cache binder LocalCallingIdentity across requests"" into rvc-dev
diff --git a/apex/framework/Android.bp b/apex/framework/Android.bp
index 6f0aeba..62104a2 100644
--- a/apex/framework/Android.bp
+++ b/apex/framework/Android.bp
@@ -55,7 +55,6 @@
     name: "framework-mediaprovider-stubs-srcs-defaults",
     srcs: [
         ":framework-mediaprovider-sources",
-        ":framework-mediaprovider-annotation-sources",
     ],
     sdk_version: "system_current",
 }
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
new file mode 100644
index 0000000..52439fb
--- /dev/null
+++ b/apex/framework/api/current.txt
@@ -0,0 +1,404 @@
+// Signature format: 2.0
+package android.provider {
+
+  public final class MediaStore {
+    ctor public MediaStore();
+    method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+    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 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);
+    method public static android.net.Uri getMediaScannerUri();
+    method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
+    method public static boolean getRequireOriginal(@NonNull android.net.Uri);
+    method @NonNull public static String getVersion(@NonNull android.content.Context);
+    method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
+    method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
+    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";
+    field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
+    field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
+    field public static final String ACTION_REVIEW_SECURE = "android.provider.action.REVIEW_SECURE";
+    field public static final String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
+    field public static final String AUTHORITY = "media";
+    field @NonNull public static final android.net.Uri AUTHORITY_URI;
+    field public static final String EXTRA_BRIGHTNESS = "android.provider.extra.BRIGHTNESS";
+    field public static final String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
+    field public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
+    field public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
+    field public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
+    field public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
+    field public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
+    field public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
+    field public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
+    field public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
+    field public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
+    field public static final String EXTRA_OUTPUT = "output";
+    field public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
+    field public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
+    field public static final String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
+    field public static final String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
+    field public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = "android.media.action.MEDIA_PLAY_FROM_SEARCH";
+    field public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
+    field @Deprecated public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
+    field public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
+    field public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = "android.media.action.STILL_IMAGE_CAMERA_SECURE";
+    field public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH = "android.media.action.TEXT_OPEN_FROM_SEARCH";
+    field public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
+    field public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH = "android.media.action.VIDEO_PLAY_FROM_SEARCH";
+    field public static final int MATCH_DEFAULT = 0; // 0x0
+    field public static final int MATCH_EXCLUDE = 2; // 0x2
+    field public static final int MATCH_INCLUDE = 1; // 0x1
+    field public static final int MATCH_ONLY = 3; // 0x3
+    field public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
+    field public static final String MEDIA_SCANNER_VOLUME = "volume";
+    field public static final String META_DATA_REVIEW_GALLERY_PREWARM_SERVICE = "android.media.review_gallery_prewarm_service";
+    field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
+    field public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
+    field public static final String QUERY_ARG_MATCH_PENDING = "android:query-arg-match-pending";
+    field public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
+    field public static final String QUERY_ARG_RELATED_URI = "android:query-arg-related-uri";
+    field public static final String UNKNOWN_STRING = "<unknown>";
+    field public static final String VOLUME_EXTERNAL = "external";
+    field public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary";
+    field public static final String VOLUME_INTERNAL = "internal";
+  }
+
+  public static final class MediaStore.Audio {
+    ctor public MediaStore.Audio();
+    method @Deprecated @Nullable public static String keyFor(@Nullable String);
+  }
+
+  public static interface MediaStore.Audio.AlbumColumns {
+    field public static final String ALBUM = "album";
+    field @Deprecated public static final String ALBUM_ART = "album_art";
+    field public static final String ALBUM_ID = "album_id";
+    field @Deprecated public static final String ALBUM_KEY = "album_key";
+    field public static final String ARTIST = "artist";
+    field public static final String ARTIST_ID = "artist_id";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
+    field public static final String FIRST_YEAR = "minyear";
+    field public static final String LAST_YEAR = "maxyear";
+    field public static final String NUMBER_OF_SONGS = "numsongs";
+    field public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
+  }
+
+  public static final class MediaStore.Audio.Albums implements android.provider.BaseColumns android.provider.MediaStore.Audio.AlbumColumns {
+    ctor public MediaStore.Audio.Albums();
+    method public static android.net.Uri getContentUri(String);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
+    field public static final String DEFAULT_SORT_ORDER = "album_key";
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  public static interface MediaStore.Audio.ArtistColumns {
+    field public static final String ARTIST = "artist";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
+    field public static final String NUMBER_OF_ALBUMS = "number_of_albums";
+    field public static final String NUMBER_OF_TRACKS = "number_of_tracks";
+  }
+
+  public static final class MediaStore.Audio.Artists implements android.provider.BaseColumns android.provider.MediaStore.Audio.ArtistColumns {
+    ctor public MediaStore.Audio.Artists();
+    method public static android.net.Uri getContentUri(String);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
+    field public static final String DEFAULT_SORT_ORDER = "artist_key";
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  public static final class MediaStore.Audio.Artists.Albums implements android.provider.MediaStore.Audio.AlbumColumns {
+    ctor public MediaStore.Audio.Artists.Albums();
+    method public static android.net.Uri getContentUri(String, long);
+  }
+
+  public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String ALBUM_ID = "album_id";
+    field @Deprecated public static final String ALBUM_KEY = "album_key";
+    field public static final String ARTIST_ID = "artist_id";
+    field @Deprecated public static final String ARTIST_KEY = "artist_key";
+    field public static final String BOOKMARK = "bookmark";
+    field public static final String GENRE = "genre";
+    field public static final String GENRE_ID = "genre_id";
+    field @Deprecated public static final String GENRE_KEY = "genre_key";
+    field public static final String IS_ALARM = "is_alarm";
+    field public static final String IS_AUDIOBOOK = "is_audiobook";
+    field public static final String IS_MUSIC = "is_music";
+    field public static final String IS_NOTIFICATION = "is_notification";
+    field public static final String IS_PODCAST = "is_podcast";
+    field public static final String IS_RINGTONE = "is_ringtone";
+    field @Deprecated public static final String TITLE_KEY = "title_key";
+    field public static final String TITLE_RESOURCE_URI = "title_resource_uri";
+    field public static final String TRACK = "track";
+    field public static final String YEAR = "year";
+  }
+
+  public static final class MediaStore.Audio.Genres implements android.provider.BaseColumns android.provider.MediaStore.Audio.GenresColumns {
+    ctor public MediaStore.Audio.Genres();
+    method public static android.net.Uri getContentUri(String);
+    method public static android.net.Uri getContentUriForAudioId(String, int);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
+    field public static final String DEFAULT_SORT_ORDER = "name";
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  public static final class MediaStore.Audio.Genres.Members implements android.provider.MediaStore.Audio.AudioColumns {
+    ctor public MediaStore.Audio.Genres.Members();
+    method public static android.net.Uri getContentUri(String, long);
+    field public static final String AUDIO_ID = "audio_id";
+    field public static final String CONTENT_DIRECTORY = "members";
+    field public static final String DEFAULT_SORT_ORDER = "title_key";
+    field public static final String GENRE_ID = "genre_id";
+  }
+
+  public static interface MediaStore.Audio.GenresColumns {
+    field public static final String NAME = "name";
+  }
+
+  public static final class MediaStore.Audio.Media implements android.provider.MediaStore.Audio.AudioColumns {
+    ctor public MediaStore.Audio.Media();
+    method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
+    method @Deprecated @Nullable public static android.net.Uri getContentUriForPath(@NonNull String);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
+    field public static final String DEFAULT_SORT_ORDER = "title_key";
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final String EXTRA_MAX_BYTES = "android.provider.MediaStore.extra.MAX_BYTES";
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field public static final String RECORD_SOUND_ACTION = "android.provider.MediaStore.RECORD_SOUND";
+  }
+
+  public static final class MediaStore.Audio.Playlists implements android.provider.BaseColumns android.provider.MediaStore.Audio.PlaylistsColumns {
+    ctor public MediaStore.Audio.Playlists();
+    method public static android.net.Uri getContentUri(String);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
+    field public static final String DEFAULT_SORT_ORDER = "name";
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  public static final class MediaStore.Audio.Playlists.Members implements android.provider.MediaStore.Audio.AudioColumns {
+    ctor public MediaStore.Audio.Playlists.Members();
+    method public static android.net.Uri getContentUri(String, long);
+    method public static boolean moveItem(android.content.ContentResolver, long, int, int);
+    field public static final String AUDIO_ID = "audio_id";
+    field public static final String CONTENT_DIRECTORY = "members";
+    field public static final String DEFAULT_SORT_ORDER = "play_order";
+    field public static final String PLAYLIST_ID = "playlist_id";
+    field public static final String PLAY_ORDER = "play_order";
+    field public static final String _ID = "_id";
+  }
+
+  public static interface MediaStore.Audio.PlaylistsColumns extends android.provider.MediaStore.MediaColumns {
+    field @Deprecated public static final String DATA = "_data";
+    field public static final String DATE_ADDED = "date_added";
+    field public static final String DATE_MODIFIED = "date_modified";
+    field public static final String NAME = "name";
+  }
+
+  public static final class MediaStore.Audio.Radio {
+    field public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
+  }
+
+  public static interface MediaStore.DownloadColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String DOWNLOAD_URI = "download_uri";
+    field public static final String REFERER_URI = "referer_uri";
+  }
+
+  public static final class MediaStore.Downloads implements android.provider.MediaStore.DownloadColumns {
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download";
+    field @NonNull public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @NonNull public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  public static final class MediaStore.Files {
+    ctor public MediaStore.Files();
+    method public static android.net.Uri getContentUri(String);
+    method public static android.net.Uri getContentUri(String, long);
+  }
+
+  public static interface MediaStore.Files.FileColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String MEDIA_TYPE = "media_type";
+    field public static final int MEDIA_TYPE_AUDIO = 2; // 0x2
+    field public static final int MEDIA_TYPE_DOCUMENT = 6; // 0x6
+    field public static final int MEDIA_TYPE_IMAGE = 1; // 0x1
+    field public static final int MEDIA_TYPE_NONE = 0; // 0x0
+    field public static final int MEDIA_TYPE_PLAYLIST = 4; // 0x4
+    field public static final int MEDIA_TYPE_SUBTITLE = 5; // 0x5
+    field public static final int MEDIA_TYPE_VIDEO = 3; // 0x3
+    field public static final String MIME_TYPE = "mime_type";
+    field public static final String PARENT = "parent";
+  }
+
+  public static final class MediaStore.Images {
+    ctor public MediaStore.Images();
+  }
+
+  public static interface MediaStore.Images.ImageColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String DESCRIPTION = "description";
+    field public static final String EXPOSURE_TIME = "exposure_time";
+    field public static final String F_NUMBER = "f_number";
+    field public static final String ISO = "iso";
+    field public static final String IS_PRIVATE = "isprivate";
+    field @Deprecated public static final String LATITUDE = "latitude";
+    field @Deprecated public static final String LONGITUDE = "longitude";
+    field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
+    field @Deprecated public static final String PICASA_ID = "picasa_id";
+    field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
+  }
+
+  public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
+    ctor public MediaStore.Images.Media();
+    method @Deprecated public static android.graphics.Bitmap getBitmap(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException, java.io.IOException;
+    method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
+    method @Deprecated public static String insertImage(android.content.ContentResolver, String, String, String) throws java.io.FileNotFoundException;
+    method @Deprecated public static String insertImage(android.content.ContentResolver, android.graphics.Bitmap, String, String);
+    method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[], String, String);
+    method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[], String, String[], String);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
+    field public static final String DEFAULT_SORT_ORDER = "bucket_display_name";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  @Deprecated public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Images.Thumbnails();
+    method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
+    method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
+    method @Deprecated public static android.net.Uri getContentUri(String);
+    method @Deprecated @NonNull public static android.util.Size getKindSize(int);
+    method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
+    method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
+    method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, String[]);
+    method @Deprecated public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, String[]);
+    field @Deprecated public static final String DATA = "_data";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "image_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final String IMAGE_ID = "image_id";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String THUMB_DATA = "thumb_data";
+    field @Deprecated public static final String WIDTH = "width";
+  }
+
+  public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
+    field public static final String ALBUM = "album";
+    field public static final String ALBUM_ARTIST = "album_artist";
+    field public static final String ARTIST = "artist";
+    field public static final String AUTHOR = "author";
+    field public static final String BITRATE = "bitrate";
+    field public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+    field public static final String BUCKET_ID = "bucket_id";
+    field public static final String CAPTURE_FRAMERATE = "capture_framerate";
+    field public static final String CD_TRACK_NUMBER = "cd_track_number";
+    field public static final String COMPILATION = "compilation";
+    field public static final String COMPOSER = "composer";
+    field @Deprecated public static final String DATA = "_data";
+    field public static final String DATE_ADDED = "date_added";
+    field public static final String DATE_EXPIRES = "date_expires";
+    field public static final String DATE_MODIFIED = "date_modified";
+    field public static final String DATE_TAKEN = "datetaken";
+    field public static final String DISC_NUMBER = "disc_number";
+    field public static final String DISPLAY_NAME = "_display_name";
+    field public static final String DOCUMENT_ID = "document_id";
+    field public static final String DURATION = "duration";
+    field public static final String GENERATION_ADDED = "generation_added";
+    field public static final String GENERATION_MODIFIED = "generation_modified";
+    field public static final String GENRE = "genre";
+    field public static final String HEIGHT = "height";
+    field public static final String INSTANCE_ID = "instance_id";
+    field public static final String IS_DOWNLOAD = "is_download";
+    field public static final String IS_DRM = "is_drm";
+    field public static final String IS_FAVORITE = "is_favorite";
+    field public static final String IS_PENDING = "is_pending";
+    field public static final String IS_TRASHED = "is_trashed";
+    field public static final String MIME_TYPE = "mime_type";
+    field public static final String NUM_TRACKS = "num_tracks";
+    field public static final String ORIENTATION = "orientation";
+    field public static final String ORIGINAL_DOCUMENT_ID = "original_document_id";
+    field public static final String OWNER_PACKAGE_NAME = "owner_package_name";
+    field public static final String RELATIVE_PATH = "relative_path";
+    field public static final String RESOLUTION = "resolution";
+    field public static final String SIZE = "_size";
+    field public static final String TITLE = "title";
+    field public static final String VOLUME_NAME = "volume_name";
+    field public static final String WIDTH = "width";
+    field public static final String WRITER = "writer";
+    field public static final String XMP = "xmp";
+    field public static final String YEAR = "year";
+  }
+
+  public static final class MediaStore.Video {
+    ctor public MediaStore.Video();
+    method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
+    field public static final String DEFAULT_SORT_ORDER = "_display_name";
+  }
+
+  public static final class MediaStore.Video.Media implements android.provider.MediaStore.Video.VideoColumns {
+    ctor public MediaStore.Video.Media();
+    method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
+    field public static final String DEFAULT_SORT_ORDER = "title";
+    field public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field public static final android.net.Uri INTERNAL_CONTENT_URI;
+  }
+
+  @Deprecated public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns {
+    ctor @Deprecated public MediaStore.Video.Thumbnails();
+    method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
+    method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
+    method @Deprecated public static android.net.Uri getContentUri(String);
+    method @Deprecated @NonNull public static android.util.Size getKindSize(int);
+    method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
+    method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
+    field @Deprecated public static final String DATA = "_data";
+    field @Deprecated public static final String DEFAULT_SORT_ORDER = "video_id ASC";
+    field @Deprecated public static final android.net.Uri EXTERNAL_CONTENT_URI;
+    field @Deprecated public static final int FULL_SCREEN_KIND = 2; // 0x2
+    field @Deprecated public static final String HEIGHT = "height";
+    field @Deprecated public static final android.net.Uri INTERNAL_CONTENT_URI;
+    field @Deprecated public static final String KIND = "kind";
+    field @Deprecated public static final int MICRO_KIND = 3; // 0x3
+    field @Deprecated public static final int MINI_KIND = 1; // 0x1
+    field @Deprecated public static final String VIDEO_ID = "video_id";
+    field @Deprecated public static final String WIDTH = "width";
+  }
+
+  public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String BOOKMARK = "bookmark";
+    field public static final String CATEGORY = "category";
+    field public static final String COLOR_RANGE = "color_range";
+    field public static final String COLOR_STANDARD = "color_standard";
+    field public static final String COLOR_TRANSFER = "color_transfer";
+    field public static final String DESCRIPTION = "description";
+    field public static final String IS_PRIVATE = "isprivate";
+    field public static final String LANGUAGE = "language";
+    field @Deprecated public static final String LATITUDE = "latitude";
+    field @Deprecated public static final String LONGITUDE = "longitude";
+    field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
+    field public static final String TAGS = "tags";
+  }
+
+}
+
diff --git a/apex/framework/api/module-lib-current.txt b/apex/framework/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apex/framework/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/framework/api/module-lib-removed.txt b/apex/framework/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apex/framework/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/framework/api/removed.txt b/apex/framework/api/removed.txt
new file mode 100644
index 0000000..183a7c9
--- /dev/null
+++ b/apex/framework/api/removed.txt
@@ -0,0 +1,43 @@
+// Signature format: 2.0
+package android.provider {
+
+  public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String ALBUM = "album";
+    field public static final String ARTIST = "artist";
+    field public static final String COMPOSER = "composer";
+    field public static final String DURATION = "duration";
+  }
+
+  public static interface MediaStore.DownloadColumns extends android.provider.MediaStore.MediaColumns {
+    field @Deprecated public static final String DESCRIPTION = "description";
+  }
+
+  public static interface MediaStore.Files.FileColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String TITLE = "title";
+  }
+
+  public static interface MediaStore.Images.ImageColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+    field public static final String BUCKET_ID = "bucket_id";
+    field public static final String DATE_TAKEN = "datetaken";
+    field public static final String GROUP_ID = "group_id";
+    field public static final String ORIENTATION = "orientation";
+  }
+
+  public static interface MediaStore.MediaColumns extends android.provider.BaseColumns {
+    field @Deprecated public static final String GROUP_ID = "group_id";
+  }
+
+  public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
+    field public static final String ALBUM = "album";
+    field public static final String ARTIST = "artist";
+    field public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
+    field public static final String BUCKET_ID = "bucket_id";
+    field public static final String DATE_TAKEN = "datetaken";
+    field public static final String DURATION = "duration";
+    field public static final String GROUP_ID = "group_id";
+    field public static final String RESOLUTION = "resolution";
+  }
+
+}
+
diff --git a/apex/framework/api/system-current.txt b/apex/framework/api/system-current.txt
new file mode 100644
index 0000000..5ce4218
--- /dev/null
+++ b/apex/framework/api/system-current.txt
@@ -0,0 +1,14 @@
+// Signature format: 2.0
+package android.provider {
+
+  public final class MediaStore {
+    method @NonNull public static android.net.Uri rewriteToLegacy(@NonNull android.net.Uri);
+    method @NonNull @WorkerThread public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+    method @WorkerThread public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+    method @WorkerThread public static void waitForIdle(@NonNull android.content.ContentResolver);
+    field public static final String AUTHORITY_LEGACY = "media_legacy";
+    field @NonNull public static final android.net.Uri AUTHORITY_LEGACY_URI;
+  }
+
+}
+
diff --git a/apex/framework/api/system-removed.txt b/apex/framework/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/apex/framework/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/gen_strings.py b/gen_strings.py
old mode 100644
new mode 100755
index ee06841..99bba8a
--- a/gen_strings.py
+++ b/gen_strings.py
@@ -29,7 +29,7 @@
 for verb in verbs:
     verblabel = verb
     if verb == "write":
-        verblabel = "change"
+        verblabel = "modify"
 
     verblabelcaps = verblabel[0].upper() + verblabel[1:]
     if verb == "trash":
@@ -45,15 +45,8 @@
             print Template('''
 <!-- Dialog title asking if user will allow $verb permission to the $data item displayed below this string. [CHAR LIMIT=128] -->
 <plurals name="permission_${verb}_${data}">
-    <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this $datalabel to trash?</item>
-    <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s to trash?</item>
-</plurals>
-''').substitute(vars()).strip("\n")
-            print Template('''
-<!-- Dialog body text explaining that this $data item will be permanently deleted after the shown duration. [CHAR LIMIT=128] -->
-<plurals name="permission_${verb}_${data}_info">
-    <item quantity="one">This $datalabel will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-    <item quantity="other">These ${datalabel}s will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
+    <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this $datalabel to trash?</item>
+    <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s to trash?</item>
 </plurals>
 ''').substitute(vars()).strip("\n")
 
@@ -61,8 +54,8 @@
             print Template('''
 <!-- Dialog title asking if user will allow $verb permission to the $data item displayed below this string. [CHAR LIMIT=128] -->
 <plurals name="permission_${verb}_${data}">
-    <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this $datalabel out of trash?</item>
-    <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s out of trash?</item>
+    <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this $datalabel out of trash?</item>
+    <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s out of trash?</item>
 </plurals>
 ''').substitute(vars()).strip("\n")
 
@@ -70,19 +63,11 @@
             print Template('''
 <!-- Dialog title asking if user will allow $verb permission to the $data item displayed below this string. [CHAR LIMIT=128] -->
 <plurals name="permission_${verb}_${data}">
-    <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> $verblabel this $datalabel?</item>
-    <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> $verblabel <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s?</item>
+    <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to $verblabel this $datalabel?</item>
+    <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to $verblabel <xliff:g id="count" example="42">^2</xliff:g> ${datalabel}s?</item>
 </plurals>
 ''').substitute(vars()).strip("\n")
 
-
-    print Template('''
-<!-- Positive dialog button confirming that $verb permission should be granted. [CHAR LIMIT=32] -->
-<string name="permission_${verb}_grant">${verblabelcaps}</string>
-<!-- Negative dialog button confirming that $verb permission should not be granted. [CHAR LIMIT=32] -->
-<string name="permission_${verb}_deny">Cancel</string>
-''').substitute(vars()).strip("\n")
-
 print '''
 <!-- ========================= END AUTO-GENERATED BY gen_strings.py ========================= -->
 '''
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index a014489..d014a8e 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -816,7 +816,7 @@
     // FUSE after that write may be served from cache
     bool direct_io = ri->isRedactionNeeded() || is_file_locked(fd, path);
 
-    handle* h = new handle(path, fd, ri, /*owner_uid*/ -1, !direct_io);
+    handle* h = new handle(path, fd, ri, !direct_io);
     node->AddHandle(h);
     return h;
 }
@@ -1080,11 +1080,6 @@
 
     fuse->fadviser.Close(h->fd);
     if (node) {
-        // TODO(b/145737191) Legacy apps don't expect FuseDaemon to update database.
-        // Inserting/deleting the database entry might break app's functionality.
-        // if (h->owner_uid != -1) {
-        //    fuse->mp->ScanFile(h->path, h->owner_uid);
-        // }
         node->DestroyHandle(h);
     }
 
diff --git a/jni/MediaProviderWrapper.cpp b/jni/MediaProviderWrapper.cpp
index 0cdfd87..77e1470 100644
--- a/jni/MediaProviderWrapper.cpp
+++ b/jni/MediaProviderWrapper.cpp
@@ -116,9 +116,9 @@
 }
 
 void scanFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_scan_file,
-                      const string& path, uid_t uid) {
+                      const string& path) {
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
-    env->CallVoidMethod(media_provider_object, mid_scan_file, j_path.get(), uid);
+    env->CallVoidMethod(media_provider_object, mid_scan_file, j_path.get());
     CheckForJniException(env);
 }
 
@@ -241,7 +241,7 @@
     mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I", /*is_static*/ false);
     mid_is_open_allowed_ = CacheMethod(env, "isOpenAllowed", "(Ljava/lang/String;IZ)I",
                                        /*is_static*/ false);
-    mid_scan_file_ = CacheMethod(env, "scanFile", "(Ljava/lang/String;I)V",
+    mid_scan_file_ = CacheMethod(env, "scanFile", "(Ljava/lang/String;)V",
                                  /*is_static*/ false);
     mid_is_mkdir_or_rmdir_allowed_ = CacheMethod(env, "isDirectoryCreationOrDeletionAllowed",
                                                  "(Ljava/lang/String;IZ)I", /*is_static*/ false);
@@ -290,7 +290,7 @@
 int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) {
     if (shouldBypassMediaProvider(uid)) {
         int res = unlink(path.c_str());
-        ScanFile(path, uid);
+        ScanFile(path);
         return res;
     }
 
@@ -308,15 +308,9 @@
                                  for_write);
 }
 
-void MediaProviderWrapper::ScanFile(const string& path, uid_t uid) {
-    if (uid == 0) {
-        // uid = 0 is kernel or adb shell running as root and doesn't have a package associated with
-        // it. This uid should get global access hence use MediaProvider's uid.
-        uid = getuid();
-    }
-
+void MediaProviderWrapper::ScanFile(const string& path) {
     JNIEnv* env = MaybeAttachCurrentThread();
-    scanFileInternal(env, media_provider_object_, mid_scan_file_, path, uid);
+    scanFileInternal(env, media_provider_object_, mid_scan_file_, path);
 }
 
 int MediaProviderWrapper::IsCreatingDirAllowed(const string& path, uid_t uid) {
diff --git a/jni/MediaProviderWrapper.h b/jni/MediaProviderWrapper.h
index 22c5b3e..016696f 100644
--- a/jni/MediaProviderWrapper.h
+++ b/jni/MediaProviderWrapper.h
@@ -105,9 +105,8 @@
      * MediaProvider database.
      *
      * @param path the path of the file to be scanned
-     * @param uid UID of the app that owns the file on the given path
      */
-    void ScanFile(const std::string& path, uid_t uid);
+    void ScanFile(const std::string& path);
 
     /**
      * Determines if the given UID is allowed to create a directory with the given path.
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 5098316..a146d20 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -36,16 +36,14 @@
 namespace fuse {
 
 struct handle {
-    explicit handle(const std::string& path, int fd, const RedactionInfo* ri, uid_t owner_uid,
-                    bool cached)
-        : path(path), fd(fd), ri(ri), owner_uid(owner_uid), cached(cached) {
+    explicit handle(const std::string& path, int fd, const RedactionInfo* ri, bool cached)
+        : path(path), fd(fd), ri(ri), cached(cached) {
         CHECK(ri != nullptr);
     }
 
     const std::string path;
     const int fd;
     const std::unique_ptr<const RedactionInfo> ri;
-    const uid_t owner_uid;
     const bool cached;
 
     ~handle() { close(fd); }
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 423af3d..fd56f49 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -213,8 +213,7 @@
 TEST_F(NodeTest, AddDestroyHandle) {
     unique_node_ptr node = CreateNode(nullptr, "/path");
 
-    handle* h = new handle("/path", -1, new mediaprovider::fuse::RedactionInfo, getuid(),
-                           true /* cached */);
+    handle* h = new handle("/path", -1, new mediaprovider::fuse::RedactionInfo, true /* cached */);
     node->AddHandle(h);
     ASSERT_TRUE(node->HasCachedHandle());
 
@@ -225,7 +224,7 @@
     // the node in question.
     EXPECT_DEATH(node->DestroyHandle(h), "");
     EXPECT_DEATH(node->DestroyHandle(nullptr), "");
-    std::unique_ptr<handle> h2(new handle("/path2", -1, new mediaprovider::fuse::RedactionInfo,
-                                          getuid(), true /* cached */));
+    std::unique_ptr<handle> h2(
+            new handle("/path2", -1, new mediaprovider::fuse::RedactionInfo, true /* cached */));
     EXPECT_DEATH(node->DestroyHandle(h2.get()), "");
 }
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 04cab66..ef51112 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -51,6 +51,7 @@
 
     <!-- Text placed over a visual thumbnail indicating that there are more items beyond the number currently displayed on the screen. [CHAR LIMIT=6] -->
     <plurals name="permission_more_thumb">
+        <item quantity="one">+<xliff:g id="count" example="1">^1</xliff:g></item>
         <item quantity="other">+<xliff:g id="count" example="42">^1</xliff:g></item>
     </plurals>
 
@@ -61,10 +62,10 @@
     </plurals>
 
     <!-- Cache clearing permission dialog warning title. [CHAR LIMIT=NONE] -->
-    <string name="cache_clearing_dialog_title">Clear app cache with <xliff:g id="app_seeking_permission" example="File manager">%s</xliff:g>?</string>
+    <string name="cache_clearing_dialog_title">Clear temporary app files?</string>
 
     <!-- Cache clearing permission dialog warning text. [CHAR LIMIT=NONE] -->
-    <string name="cache_clearing_dialog_text"><xliff:g id="app_seeking_permission" example="File manager">%s</xliff:g> would like to clear some temporary files. Accepting may increase battery or data use.</string>
+    <string name="cache_clearing_dialog_text"><xliff:g id="app_seeking_permission" example="File manager">%s</xliff:g> would like to clear some temporary files. This may result in an increased usage of battery or cellular data.</string>
 
     <!-- Allow dialog action text. [CHAR LIMIT=30] -->
     <string name="allow">Allow</string>
@@ -78,129 +79,93 @@
 
     <!-- Dialog title asking if user will allow write permission to the audio item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_write_audio">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change this audio file?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change <xliff:g id="count" example="42">^2</xliff:g> audio files?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify this audio file?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify <xliff:g id="count" example="42">^2</xliff:g> audio files?</item>
     </plurals>
     <!-- Dialog title asking if user will allow write permission to the video item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_write_video">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change this video?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change <xliff:g id="count" example="42">^2</xliff:g> videos?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify this video?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify <xliff:g id="count" example="42">^2</xliff:g> videos?</item>
     </plurals>
     <!-- Dialog title asking if user will allow write permission to the image item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_write_image">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change this photo?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change <xliff:g id="count" example="42">^2</xliff:g> photos?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify this photo?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify <xliff:g id="count" example="42">^2</xliff:g> photos?</item>
     </plurals>
     <!-- Dialog title asking if user will allow write permission to the generic item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_write_generic">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change this item?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> change <xliff:g id="count" example="42">^2</xliff:g> items?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify this item?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to modify <xliff:g id="count" example="42">^2</xliff:g> items?</item>
     </plurals>
-    <!-- Positive dialog button confirming that write permission should be granted. [CHAR LIMIT=32] -->
-    <string name="permission_write_grant">Change</string>
-    <!-- Negative dialog button confirming that write permission should not be granted. [CHAR LIMIT=32] -->
-    <string name="permission_write_deny">Cancel</string>
 
     <!-- ========================= TRASH STRINGS ========================= -->
 
     <!-- Dialog title asking if user will allow trash permission to the audio item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_trash_audio">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this audio file to trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> audio files to trash?</item>
-    </plurals>
-    <!-- Dialog body text explaining that this audio item will be permanently deleted after the shown duration. [CHAR LIMIT=128] -->
-    <plurals name="permission_trash_audio_info">
-        <item quantity="one">This audio file will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-        <item quantity="other">These audio files will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this audio file to trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> audio files to trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow trash permission to the video item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_trash_video">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this video to trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> videos to trash?</item>
-    </plurals>
-    <!-- Dialog body text explaining that this video item will be permanently deleted after the shown duration. [CHAR LIMIT=128] -->
-    <plurals name="permission_trash_video_info">
-        <item quantity="one">This video will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-        <item quantity="other">These videos will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this video to trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> videos to trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow trash permission to the image item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_trash_image">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this photo to trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> photos to trash?</item>
-    </plurals>
-    <!-- Dialog body text explaining that this image item will be permanently deleted after the shown duration. [CHAR LIMIT=128] -->
-    <plurals name="permission_trash_image_info">
-        <item quantity="one">This photo will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-        <item quantity="other">These photos will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this photo to trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> photos to trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow trash permission to the generic item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_trash_generic">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this item to trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> items to trash?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this item to trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> items to trash?</item>
     </plurals>
-    <!-- Dialog body text explaining that this generic item will be permanently deleted after the shown duration. [CHAR LIMIT=128] -->
-    <plurals name="permission_trash_generic_info">
-        <item quantity="one">This item will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-        <item quantity="other">These items will be permanently deleted after <xliff:g id="duration" example="42">^3</xliff:g> days</item>
-    </plurals>
-    <!-- Positive dialog button confirming that trash permission should be granted. [CHAR LIMIT=32] -->
-    <string name="permission_trash_grant">Move to trash</string>
-    <!-- Negative dialog button confirming that trash permission should not be granted. [CHAR LIMIT=32] -->
-    <string name="permission_trash_deny">Cancel</string>
 
     <!-- ========================= UNTRASH STRINGS ========================= -->
 
     <!-- Dialog title asking if user will allow untrash permission to the audio item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_untrash_audio">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this audio file out of trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> audio files out of trash?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this audio file out of trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> audio files out of trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow untrash permission to the video item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_untrash_video">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this video out of trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> videos out of trash?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this video out of trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> videos out of trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow untrash permission to the image item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_untrash_image">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this photo out of trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> photos out of trash?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this photo out of trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> photos out of trash?</item>
     </plurals>
     <!-- Dialog title asking if user will allow untrash permission to the generic item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_untrash_generic">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move this item out of trash?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> move <xliff:g id="count" example="42">^2</xliff:g> items out of trash?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move this item out of trash?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to move <xliff:g id="count" example="42">^2</xliff:g> items out of trash?</item>
     </plurals>
-    <!-- Positive dialog button confirming that untrash permission should be granted. [CHAR LIMIT=32] -->
-    <string name="permission_untrash_grant">Move out of trash</string>
-    <!-- Negative dialog button confirming that untrash permission should not be granted. [CHAR LIMIT=32] -->
-    <string name="permission_untrash_deny">Cancel</string>
 
     <!-- ========================= DELETE STRINGS ========================= -->
 
     <!-- Dialog title asking if user will allow delete permission to the audio item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_delete_audio">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete this audio file?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete <xliff:g id="count" example="42">^2</xliff:g> audio files?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete this audio file?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete <xliff:g id="count" example="42">^2</xliff:g> audio files?</item>
     </plurals>
     <!-- Dialog title asking if user will allow delete permission to the video item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_delete_video">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete this video?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete <xliff:g id="count" example="42">^2</xliff:g> videos?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete this video?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete <xliff:g id="count" example="42">^2</xliff:g> videos?</item>
     </plurals>
     <!-- Dialog title asking if user will allow delete permission to the image item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_delete_image">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete this photo?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete <xliff:g id="count" example="42">^2</xliff:g> photos?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete this photo?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete <xliff:g id="count" example="42">^2</xliff:g> photos?</item>
     </plurals>
     <!-- Dialog title asking if user will allow delete permission to the generic item displayed below this string. [CHAR LIMIT=128] -->
     <plurals name="permission_delete_generic">
-        <item quantity="one">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete this item?</item>
-        <item quantity="other">Let <xliff:g id="app_name" example="Gmail">^1</xliff:g> delete <xliff:g id="count" example="42">^2</xliff:g> items?</item>
+        <item quantity="one">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete this item?</item>
+        <item quantity="other">Allow <xliff:g id="app_name" example="Gmail">^1</xliff:g> to delete <xliff:g id="count" example="42">^2</xliff:g> items?</item>
     </plurals>
-    <!-- Positive dialog button confirming that delete permission should be granted. [CHAR LIMIT=32] -->
-    <string name="permission_delete_grant">Delete</string>
-    <!-- Negative dialog button confirming that delete permission should not be granted. [CHAR LIMIT=32] -->
-    <string name="permission_delete_deny">Cancel</string>
 
     <!-- ========================= END AUTO-GENERATED BY gen_strings.py ========================= -->
 
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index ef6c27f..f767dcd 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -37,8 +37,6 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.content.ContentProvider;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -69,39 +67,13 @@
     }
 
     /**
-     * Scoped Storage is on by default. However, it is not strictly enforced and there are multiple
-     * ways to opt out of scoped storage:
-     * <ul>
-     * <li>Target Sdk < Q</li>
-     * <li>Target Sdk = Q and has `requestLegacyExternalStorage` set in AndroidManifest.xml</li>
-     * <li>Target Sdk > Q: Upgrading from an app that was opted out of scoped storage and has
-     * `preserveLegacyExternalStorage` set in AndroidManifest.xml</li>
-     * </ul>
-     * This flag is enabled for all apps by default as Scoped Storage is enabled by default.
-     * Developers can disable this flag to opt out of Scoped Storage and have legacy storage
-     * workflow.
-     *
-     * Note: {@code FORCE_ENABLE_SCOPED_STORAGE} should also be disabled for apps to opt out of
-     * scoped storage.
-     * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+     * See definition in {@link android.os.Environment}
      */
-    @ChangeId
     private static final long DEFAULT_SCOPED_STORAGE = 149924527L;
 
     /**
-     * Setting this flag strictly enforces Scoped Storage regardless of:
-     * <ul>
-     * <li>The value of Target Sdk</li>
-     * <li>The value of `requestLegacyExternalStorage` in AndroidManifest.xml</li>
-     * <li>The value of `preserveLegacyExternalStorage` in AndroidManifest.xml</li>
-     * </ul>
-     *
-     * Note: {@code DEFAULT_SCOPED_STORAGE} should also be enabled for apps to be enforced into
-     * scoped storage.
-     * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+     * See definition in {@link android.os.Environment}
      */
-    @ChangeId
-    @Disabled
     private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L;
 
     public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider) {
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 67b7d86..79357c7 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -64,7 +64,10 @@
 import static com.android.providers.media.util.FileUtils.extractRelativePathForDirectory;
 import static com.android.providers.media.util.FileUtils.extractTopLevelDir;
 import static com.android.providers.media.util.FileUtils.extractVolumeName;
+import static com.android.providers.media.util.FileUtils.getAbsoluteSanitizedPath;
 import static com.android.providers.media.util.FileUtils.isDownload;
+import static com.android.providers.media.util.FileUtils.sanitizeDisplayName;
+import static com.android.providers.media.util.FileUtils.sanitizePath;
 import static com.android.providers.media.util.Logging.LOGV;
 import static com.android.providers.media.util.Logging.TAG;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
@@ -997,15 +1000,12 @@
     /**
      * Makes MediaScanner scan the given file.
      * @param file path of the file to be scanned
-     * @param uid  UID of the app that owns the file on the given path. If the file is scanned
-     *            on create, this UID will be used for updating owner package.
      *
      * Called from JNI in jni/MediaProviderWrapper.cpp
      */
     @Keep
-    public void scanFileForFuse(String file, int uid) {
-        final String callingPackage = getCachedCallingIdentityForFuse(uid).getPackageName();
-        scanFile(new File(file), REASON_DEMAND, callingPackage);
+    public void scanFileForFuse(String file) {
+        scanFile(new File(file), REASON_DEMAND);
     }
 
     /**
@@ -2301,34 +2301,6 @@
         Trace.endSection();
     }
 
-    private static @NonNull String[] sanitizePath(@Nullable String path) {
-        if (path == null) {
-            return new String[0];
-        } else {
-            final String[] segments = path.split("/");
-            // If the path corresponds to the top level directory, then we return an empty path
-            // which denotes the top level directory
-            if (segments.length == 0) {
-                return new String[] { "" };
-            }
-            for (int i = 0; i < segments.length; i++) {
-                segments[i] = sanitizeDisplayName(segments[i]);
-            }
-            return segments;
-        }
-    }
-
-    private static @Nullable String sanitizeDisplayName(@Nullable String name) {
-        if (name == null) {
-            return null;
-        } else if (name.startsWith(".")) {
-            // The resulting file must not be hidden.
-            return FileUtils.buildValidFatFilename("_" + name);
-        } else {
-            return FileUtils.buildValidFatFilename(name);
-        }
-    }
-
     /**
      * Sanity check that any requested {@link MediaColumns#DATA} paths actually
      * live on the storage volume being targeted.
@@ -5426,16 +5398,6 @@
         }
     }
 
-    @Nullable
-    private String getAbsoluteSanitizedPath(String path) {
-        final String[] pathSegments = sanitizePath(path);
-        if (pathSegments.length == 0) {
-            return null;
-        }
-        return path = "/" + String.join("/",
-                Arrays.copyOfRange(pathSegments, 1, pathSegments.length));
-    }
-
     /**
      * Calculates the ranges that need to be redacted for the given file and user that wants to
      * access the file.
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 49a0062..371522a 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -147,8 +147,8 @@
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setTitle(resolveTitleText());
-        builder.setPositiveButton(resolvePositiveText(), this::onPositiveAction);
-        builder.setNegativeButton(resolveNegativeText(), this::onNegativeAction);
+        builder.setPositiveButton(R.string.allow, this::onPositiveAction);
+        builder.setNegativeButton(R.string.deny, this::onNegativeAction);
         builder.setCancelable(false);
         builder.setView(bodyView);
 
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 81a6a34..955c027 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -384,7 +384,8 @@
             final Bundle queryArgs = new Bundle();
             queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
                     formatClause + " AND " + dataClause + " AND " + generationClause);
-            final String pathEscapedForLike = DatabaseUtils.escapeForLike(mRoot.getAbsolutePath());
+            final String pathEscapedForLike = DatabaseUtils.escapeForLike(
+                    FileUtils.getAbsoluteSanitizedPath(mRoot.getAbsolutePath()));
             queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
                     new String[] {pathEscapedForLike + "/%", pathEscapedForLike});
             queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
diff --git a/src/com/android/providers/media/util/ExifUtils.java b/src/com/android/providers/media/util/ExifUtils.java
index 09674a8..7593ec8 100644
--- a/src/com/android/providers/media/util/ExifUtils.java
+++ b/src/com/android/providers/media/util/ExifUtils.java
@@ -32,6 +32,7 @@
 import android.annotation.Nullable;
 import android.media.ExifInterface;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 
 import java.text.ParsePosition;
@@ -49,7 +50,9 @@
     // Pattern to check non zero timestamp
     private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
 
+    @GuardedBy("sFormatter")
     private static final SimpleDateFormat sFormatter;
+    @GuardedBy("sFormatterTz")
     private static final SimpleDateFormat sFormatterTz;
 
     static {
@@ -105,7 +108,10 @@
 
         ParsePosition pos = new ParsePosition(0);
         try {
-            Date datetime = sFormatter.parse(dateTimeString, pos);
+            final Date datetime;
+            synchronized (sFormatter) {
+                datetime = sFormatter.parse(dateTimeString, pos);
+            }
             if (datetime == null) return -1;
             return datetime.getTime();
         } catch (IllegalArgumentException e) {
@@ -122,12 +128,17 @@
         try {
             // The exif field is in local time. Parsing it as if it is UTC will yield time
             // since 1/1/1970 local time
-            Date datetime = sFormatter.parse(dateTimeString, pos);
+            Date datetime;
+            synchronized (sFormatter) {
+                datetime = sFormatter.parse(dateTimeString, pos);
+            }
 
             if (offsetString != null) {
                 dateTimeString = dateTimeString + " " + offsetString;
                 ParsePosition position = new ParsePosition(0);
-                datetime = sFormatterTz.parse(dateTimeString, position);
+                synchronized (sFormatterTz) {
+                    datetime = sFormatterTz.parse(dateTimeString, position);
+                }
             }
 
             if (datetime == null) return -1;
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index b247f2e..c9cf1e2 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -701,4 +701,49 @@
             }
         }
     }
+
+    /** {@hide} **/
+    @Nullable
+    public static String getAbsoluteSanitizedPath(String path) {
+        final String[] pathSegments = sanitizePath(path);
+        if (pathSegments.length == 0) {
+            return null;
+        }
+        return path = "/" + String.join("/",
+                Arrays.copyOfRange(pathSegments, 1, pathSegments.length));
+    }
+
+    /** {@hide} */
+    public static @NonNull String[] sanitizePath(@Nullable String path) {
+        if (path == null) {
+            return new String[0];
+        } else {
+            final String[] segments = path.split("/");
+            // If the path corresponds to the top level directory, then we return an empty path
+            // which denotes the top level directory
+            if (segments.length == 0) {
+                return new String[] { "" };
+            }
+            for (int i = 0; i < segments.length; i++) {
+                segments[i] = sanitizeDisplayName(segments[i]);
+            }
+            return segments;
+        }
+    }
+
+    /**
+     * Sanitizes given name by appending '_' to make it non-hidden and mutating the file
+     * name to make it valid for a FAT filesystem.
+     * @hide
+     */
+    public static @Nullable String sanitizeDisplayName(@Nullable String name) {
+        if (name == null) {
+            return null;
+        } else if (name.startsWith(".")) {
+            // The resulting file must not be hidden.
+            return buildValidFatFilename("_" + name);
+        } else {
+            return buildValidFatFilename(name);
+        }
+    }
 }
diff --git a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
index 94c2a74..886acd8 100644
--- a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
+++ b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.UiAutomation;
 import android.content.ContentProviderClient;
@@ -32,6 +33,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.os.storage.StorageManager;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
@@ -59,6 +61,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Verify that we preserve information from the old "legacy" provider from
@@ -74,6 +77,9 @@
     // TODO: expand test to cover secondary storage devices
     private String mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
 
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+    private static final long POLLING_SLEEP_MILLIS = 100;
+
     private Uri mExternalAudio;
     private Uri mExternalVideo;
     private Uri mExternalImages;
@@ -211,9 +217,7 @@
         // Clear data on the modern provider so that the initial scan recovers
         // metadata from the legacy provider
         executeShellCommand("pm clear " + modernProvider.applicationInfo.packageName, ui);
-
-        // Sleep to give the MediaProvider time to recover from being killed after clearing data
-        Thread.sleep(5000);
+        pollForExternalStorageState();
 
         // And force a scan to confirm upgraded data survives
         MediaStore.waitForIdle(context.getContentResolver());
@@ -243,6 +247,17 @@
         }
     }
 
+    private static void pollForExternalStorageState() throws Exception {
+        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+            if(Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
+                    .equals(Environment.MEDIA_MOUNTED)) {
+                return;
+            }
+            SystemClock.sleep(POLLING_SLEEP_MILLIS);
+        }
+        fail("Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
+    }
+
     public static String executeShellCommand(String command, UiAutomation uiAutomation)
             throws IOException {
         Log.v(TAG, "$ " + command);
diff --git a/tests/src/com/android/providers/media/util/ExifUtilsTest.java b/tests/src/com/android/providers/media/util/ExifUtilsTest.java
new file mode 100644
index 0000000..2b0724c
--- /dev/null
+++ b/tests/src/com/android/providers/media/util/ExifUtilsTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.util;
+
+import android.media.ExifInterface;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.ToLongFunction;
+
+import static org.junit.Assert.assertEquals;
+
+public class ExifUtilsTest {
+    @Test
+    public void testParseDateTime() throws Exception {
+        final ExifInterface exif = createTestExif();
+        assertParseDateTime(exif, ExifUtils::getDateTimeOriginal);
+    }
+
+    @Test
+    public void testParseDateTimeTz() throws Exception {
+        final ExifInterface exif = createTestExif();
+        assertParseDateTime(exif, ExifUtils::getDateTimeDigitized);
+    }
+
+    @Test
+    public void testParseGpsDateTime() throws Exception {
+        final ExifInterface exif = createTestExif();
+        assertParseDateTime(exif, ExifUtils::getGpsDateTime);
+    }
+
+    private ExifInterface createTestExif() throws Exception {
+        final File file = File.createTempFile("test", ".jpg");
+        final ExifInterface exif = new ExifInterface(file);
+        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, "2016:01:28 09:17:34");
+        exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, "2016:01:28 09:17:34 UTC");
+        exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, "2016:01:28");
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, "09:14:00");
+        return exif;
+    }
+
+    private void assertParseDateTime(ExifInterface exif, ToLongFunction<ExifInterface> func) {
+        final int numOfThreads = 5;
+        final CountDownLatch latch = new CountDownLatch(numOfThreads);
+        final AtomicInteger count = new AtomicInteger(numOfThreads);
+
+        for (int i = 0; i < numOfThreads; i++) {
+            new Thread(() -> {
+                if (parseDateTime(exif, func)) count.decrementAndGet();
+                latch.countDown();
+            }).start();
+        }
+
+        try {
+            latch.await(10, TimeUnit.SECONDS);
+        } catch (InterruptedException ignored) {
+        }
+
+        assertEquals(0, count.get());
+    }
+
+    private boolean parseDateTime(ExifInterface exif, ToLongFunction<ExifInterface> func) {
+        final long expected = func.applyAsLong(exif);
+        try {
+            for (int i = 0; i < 1000; ++i) {
+                final long actual = func.applyAsLong(exif);
+                if (expected != actual) {
+                    return false;
+                }
+            }
+        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+            return false;
+        }
+        return true;
+    }
+}
+