1 /*
<lambda>null2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.photopicker.data
18
19 import android.database.Cursor
20 import android.database.MatrixCursor
21 import android.net.Uri
22 import android.os.Bundle
23 import android.os.CancellationSignal
24 import android.test.mock.MockContentProvider
25 import com.android.photopicker.data.model.Group
26 import com.android.photopicker.data.model.Media
27 import com.android.photopicker.data.model.MediaSource
28 import com.android.photopicker.data.model.Provider
29 import java.util.UUID
30
31 /**
32 * A test utility that provides implementation for some MediaProvider queries.
33 *
34 * This will be used to wrap [ContentResolver] to intercept calls to it and re-route them to the
35 * internal mock this class holds.
36 *
37 * All not overridden / unimplemented operations will throw [UnsupportedOperationException].
38 */
39
40 val DEFAULT_PROVIDERS: List<Provider> = listOf(
41 Provider(
42 authority = "test_authority",
43 mediaSource = MediaSource.LOCAL,
44 uid = 0
45 )
46 )
47
48 val DEFAULT_MEDIA: List<Media> = listOf(
49 createMediaImage(10),
50 createMediaImage(11),
51 createMediaImage(12),
52 createMediaImage(13),
53 createMediaImage(14),
54 )
55
56 val DEFAULT_ALBUMS: List<Group.Album> = listOf(
57 createAlbum("Favorites"),
58 createAlbum("Downloads"),
59 createAlbum("CloudAlbum"),
60 )
61
62 val DEFAULT_ALBUM_NAME = "album_id"
63
64 val DEFAULT_ALBUM_MEDIA: Map<String, List<Media>> = mapOf(
65 DEFAULT_ALBUM_NAME to DEFAULT_MEDIA
66 )
67
68 fun createMediaImage(pickerId: Long): Media {
69 return Media.Image(
70 mediaId = UUID.randomUUID().toString(),
71 pickerId = pickerId,
72 authority = "authority",
73 mediaSource = MediaSource.LOCAL,
74 mediaUri = Uri.parse("content://media/picker/authority/media/$pickerId"),
75 glideLoadableUri = Uri.parse("content://authority/media/$pickerId"),
76 dateTakenMillisLong = Long.MAX_VALUE,
77 sizeInBytes = 10,
78 mimeType = "image/*",
79 standardMimeTypeExtension = 0
80 )
81 }
82
createAlbumnull83 fun createAlbum(albumId: String): Group.Album {
84 return Group.Album(
85 id = albumId,
86 pickerId = albumId.hashCode().toLong(),
87 authority = "authority",
88 dateTakenMillisLong = Long.MAX_VALUE,
89 displayName = albumId,
90 coverUri = Uri.parse("content://media/picker/authority/media/$albumId"),
91 coverMediaSource = MediaSource.LOCAL
92 )
93 }
94
95 class TestMediaProvider(
96 var providers: List<Provider> = DEFAULT_PROVIDERS,
97 var media: List<Media> = DEFAULT_MEDIA,
98 var albums: List<Group.Album> = DEFAULT_ALBUMS,
99 var albumMedia: Map<String, List<Media>> = DEFAULT_ALBUM_MEDIA
100 ) : MockContentProvider() {
101 var lastRefreshMediaRequest: Bundle? = null
102
querynull103 override fun query (
104 uri: Uri,
105 projection: Array<String>?,
106 queryArgs: Bundle?,
107 cancellationSignal: CancellationSignal?
108 ): Cursor? {
109 return when (uri.lastPathSegment) {
110 "available_providers" -> getAvailableProviders()
111 "media" -> getMedia()
112 "album" -> getAlbums()
113 else -> {
114 val pathSegments: MutableList<String> = uri.getPathSegments()
115 if (pathSegments.size == 4 && pathSegments[2].equals("album")) {
116 // Album media query
117 return getAlbumMedia(pathSegments[3])
118 } else {
119 throw UnsupportedOperationException("Could not recognize uri $uri")
120 }
121 }
122 }
123 }
124
callnull125 override fun call (
126 authority: String,
127 method: String,
128 arg: String?,
129 extras: Bundle?
130 ): Bundle? {
131 return when (method) {
132 "picker_media_init" -> {
133 initMedia(extras)
134 null
135 }
136 else -> throw UnsupportedOperationException("Could not recognize method $method")
137 }
138 }
139
140 /**
141 * Returns a [Cursor] with the providers currently in the [providers] list.
142 */
getAvailableProvidersnull143 private fun getAvailableProviders(): Cursor {
144 val cursor = MatrixCursor(arrayOf(
145 MediaProviderClient.AvailableProviderResponse.AUTHORITY.key,
146 MediaProviderClient.AvailableProviderResponse.MEDIA_SOURCE.key,
147 MediaProviderClient.AvailableProviderResponse.UID.key
148 ))
149 providers.forEach {
150 provider ->
151 cursor.addRow(
152 arrayOf(
153 provider.authority,
154 provider.mediaSource.name,
155 provider.uid.toString()
156 )
157 )
158 }
159 return cursor
160 }
161
getMedianull162 private fun getMedia(mediaItems: List<Media> = media): Cursor {
163 val cursor = MatrixCursor(
164 arrayOf(
165 MediaProviderClient.MediaResponse.MEDIA_ID.key,
166 MediaProviderClient.MediaResponse.PICKER_ID.key,
167 MediaProviderClient.MediaResponse.AUTHORITY.key,
168 MediaProviderClient.MediaResponse.MEDIA_SOURCE.key,
169 MediaProviderClient.MediaResponse.MEDIA_URI.key,
170 MediaProviderClient.MediaResponse.LOADABLE_URI.key,
171 MediaProviderClient.MediaResponse.DATE_TAKEN.key,
172 MediaProviderClient.MediaResponse.SIZE.key,
173 MediaProviderClient.MediaResponse.MIME_TYPE.key,
174 MediaProviderClient.MediaResponse.STANDARD_MIME_TYPE_EXT.key,
175 MediaProviderClient.MediaResponse.DURATION.key,
176 )
177 )
178 mediaItems.forEach {
179 mediaItem ->
180 cursor.addRow(
181 arrayOf(
182 mediaItem.mediaId,
183 mediaItem.pickerId.toString(),
184 mediaItem.authority,
185 mediaItem.mediaSource.toString(),
186 mediaItem.mediaUri.toString(),
187 mediaItem.glideLoadableUri.toString(),
188 mediaItem.dateTakenMillisLong.toString(),
189 mediaItem.sizeInBytes.toString(),
190 mediaItem.mimeType,
191 mediaItem.standardMimeTypeExtension.toString(),
192 if (mediaItem is Media.Video) mediaItem.duration else "0"
193 )
194 )
195 }
196 return cursor
197 }
198
getAlbumsnull199 private fun getAlbums(): Cursor {
200 val cursor = MatrixCursor(
201 arrayOf(
202 MediaProviderClient.AlbumResponse.ALBUM_ID.key,
203 MediaProviderClient.AlbumResponse.PICKER_ID.key,
204 MediaProviderClient.AlbumResponse.AUTHORITY.key,
205 MediaProviderClient.AlbumResponse.DATE_TAKEN.key,
206 MediaProviderClient.AlbumResponse.ALBUM_NAME.key,
207 MediaProviderClient.AlbumResponse.UNWRAPPED_COVER_URI.key,
208 MediaProviderClient.AlbumResponse.COVER_MEDIA_SOURCE.key,
209 )
210 )
211 albums.forEach {
212 album ->
213 cursor.addRow(
214 arrayOf(
215 album.id,
216 album.pickerId.toString(),
217 album.authority,
218 album.dateTakenMillisLong.toString(),
219 album.displayName,
220 album.coverUri.toString(),
221 album.coverMediaSource.toString(),
222 )
223 )
224 }
225 return cursor
226 }
227
getAlbumMedianull228 private fun getAlbumMedia(albumId: String): Cursor? {
229 return getMedia(albumMedia.getOrDefault(albumId, emptyList()))
230 }
231
232
initMedianull233 private fun initMedia(extras: Bundle?) {
234 lastRefreshMediaRequest = extras
235 }
236 }