1 /*
2  * Copyright 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.content.ContentResolver
20 import android.net.Uri
21 import androidx.paging.PagingSource
22 import com.android.photopicker.data.model.CloudMediaProviderDetails
23 import com.android.photopicker.data.model.CollectionInfo
24 import com.android.photopicker.data.model.Group.Album
25 import com.android.photopicker.data.model.Media
26 import com.android.photopicker.data.model.MediaPageKey
27 import com.android.photopicker.data.model.Provider
28 import com.android.photopicker.data.paging.FakeInMemoryAlbumPagingSource
29 import com.android.photopicker.data.paging.FakeInMemoryMediaPagingSource
30 import kotlinx.coroutines.channels.Channel
31 import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
32 import kotlinx.coroutines.flow.MutableStateFlow
33 import kotlinx.coroutines.flow.StateFlow
34 import kotlinx.coroutines.flow.update
35 
36 /**
37  * A test implementation of [DataService] that provides fake, in memory paging sources that isolate
38  * device state from the test by providing fake data that is not backed by real media.
39  *
40  * Tests can override the size of data or the actual list of data itself by injecting the data
41  * service class into the test and changing the corresponding values.
42  */
43 class TestDataServiceImpl() : DataService {
44 
45     // Overrides for MediaPagingSource
46     var mediaSetSize: Int = FakeInMemoryMediaPagingSource.DEFAULT_SIZE
47     var mediaList: List<Media>? = null
48 
49     // Overrides for AlbumPagingSource
50     var albumSetSize: Int = FakeInMemoryAlbumPagingSource.DEFAULT_SIZE
51     var albumsList: List<Album>? = null
52 
53     // Overrides for AlbumMediaPagingSource
54     var albumMediaSetSize: Int = FakeInMemoryMediaPagingSource.DEFAULT_SIZE
55     var albumMediaList: List<Media>? = null
56 
57     val _availableProviders = MutableStateFlow<List<Provider>>(emptyList())
58     override val availableProviders: StateFlow<List<Provider>> = _availableProviders
59 
60     var allowedProviders: List<Provider> = emptyList()
61 
62     val collectionInfo: HashMap<Provider, CollectionInfo> = HashMap()
63 
64     private var _preGrantsCount = MutableStateFlow(/* default value */ 0)
65 
setAvailableProvidersnull66     fun setAvailableProviders(newProviders: List<Provider>) {
67         _availableProviders.update { newProviders }
68     }
69 
70     override val activeContentResolver: StateFlow<ContentResolver>
71         get() = TODO("Not yet implemented")
72 
73     override val preGrantedMediaCount: StateFlow<Int> = _preGrantsCount
74     override val preSelectionMediaData: StateFlow<List<Media>?> =
75         MutableStateFlow(ArrayList<Media>())
76 
setInitPreGrantsCountnull77     fun setInitPreGrantsCount(count: Int) {
78         _preGrantsCount.update { count }
79     }
80 
albumMediaPagingSourcenull81     override fun albumMediaPagingSource(album: Album): PagingSource<MediaPageKey, Media> {
82         return albumMediaList?.let { FakeInMemoryMediaPagingSource(it) }
83             ?: FakeInMemoryMediaPagingSource(albumMediaSetSize)
84     }
85 
albumPagingSourcenull86     override fun albumPagingSource(): PagingSource<MediaPageKey, Album> {
87         return albumsList?.let { FakeInMemoryAlbumPagingSource(it) }
88             ?: FakeInMemoryAlbumPagingSource(albumSetSize)
89     }
90 
cloudMediaProviderDetailsnull91     override fun cloudMediaProviderDetails(
92         authority: String
93     ): StateFlow<CloudMediaProviderDetails?> =
94         throw NotImplementedError("This method is not implemented yet.")
95 
96     override fun mediaPagingSource(): PagingSource<MediaPageKey, Media> {
97         return mediaList?.let { FakeInMemoryMediaPagingSource(it) }
98             ?: FakeInMemoryMediaPagingSource(mediaSetSize)
99     }
100 
previewMediaPagingSourcenull101     override fun previewMediaPagingSource(
102         currentSelection: Set<Media>,
103         currentDeselection: Set<Media>,
104     ): PagingSource<MediaPageKey, Media> {
105         // re-using the media source, modify as per future test usage.
106         return mediaList?.let { FakeInMemoryMediaPagingSource(it) }
107             ?: FakeInMemoryMediaPagingSource(mediaSetSize)
108     }
109 
refreshMedianull110     override suspend fun refreshMedia() =
111         throw NotImplementedError("This method is not implemented yet.")
112 
113     override suspend fun refreshAlbumMedia(album: Album) =
114         throw NotImplementedError("This method is not implemented yet.")
115 
116     override val disruptiveDataUpdateChannel = Channel<Unit>(CONFLATED)
117 
118     suspend fun sendDisruptiveDataUpdateNotification() {
119         disruptiveDataUpdateChannel.send(Unit)
120     }
121 
getCollectionInfonull122     override suspend fun getCollectionInfo(provider: Provider): CollectionInfo =
123         collectionInfo.getOrElse(provider, { CollectionInfo(provider.authority) })
124 
ensureProvidersnull125     override suspend fun ensureProviders() {}
126 
getAllAllowedProvidersnull127     override fun getAllAllowedProviders(): List<Provider> = allowedProviders
128 
129     override fun refreshPreGrantedItemsCount() {
130         // no_op
131     }
132 
fetchMediaDataForUrisnull133     override fun fetchMediaDataForUris(uris: List<Uri>) {
134         // no-op
135     }
136 }
137