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.util.Log 21 import com.android.photopicker.data.model.CollectionInfo 22 import com.android.photopicker.data.model.Provider 23 import kotlin.collections.HashMap 24 import kotlin.collections.Map 25 import kotlinx.coroutines.flow.StateFlow 26 import kotlinx.coroutines.sync.Mutex 27 import kotlinx.coroutines.sync.withLock 28 29 /** 30 * A Utility class that tracks and updates the currently known Collection Info for the given 31 * Providers. 32 */ 33 class CollectionInfoState( 34 private val mediaProviderClient: MediaProviderClient, 35 private val activeContentResolver: StateFlow<ContentResolver>, 36 private val availableProviders: StateFlow<List<Provider>> 37 ) { 38 companion object { 39 private const val TAG = "CollectionInfoState" 40 } 41 42 private val providerCollectionInfo: HashMap<Provider, CollectionInfo> = HashMap() 43 private val mutex = Mutex() 44 45 /** Clear the collection info cache. */ clearnull46 suspend fun clear() { 47 mutex.withLock { providerCollectionInfo.clear() } 48 } 49 50 /** 51 * Clears the current collection info cache and updates it with the collection info list 52 * provided in the parameters. 53 * 54 * @param collectionInfo List of the latest collection infos fetched from the data source. 55 */ updateCollectionInfonull56 suspend fun updateCollectionInfo(collectionInfo: List<CollectionInfo>) { 57 val availableProviderAuthorities: Map<String, Provider> = 58 availableProviders.value.map { it.authority to it }.toMap() 59 mutex.withLock { 60 providerCollectionInfo.clear() 61 collectionInfo.forEach { 62 if (availableProviderAuthorities.containsKey(it.authority)) { 63 providerCollectionInfo.put( 64 availableProviderAuthorities.getValue(it.authority), 65 it 66 ) 67 } 68 } 69 } 70 } 71 72 /** 73 * Tries to fetch the collection info of the given provider from cache. If it is not available, 74 * returns null. 75 */ getCachedCollectionInfonull76 suspend fun getCachedCollectionInfo(provider: Provider): CollectionInfo? { 77 mutex.withLock { 78 return providerCollectionInfo.get(provider) 79 } 80 } 81 82 /** 83 * Tries to fetch the collection info of the given provider from cache. If it is not available, 84 * updates the collection info cache from the data source and again tries to fetch the 85 * collection info from the updated cache and returns it. 86 * 87 * If it is still not available, returns a default collection info object with only the 88 * authority set. 89 */ getCollectionInfonull90 suspend fun getCollectionInfo(provider: Provider): CollectionInfo { 91 var cachedCollectionInfo = getCachedCollectionInfo(provider) 92 93 if (cachedCollectionInfo == null) { 94 try { 95 val collectionInfos = 96 mediaProviderClient.fetchCollectionInfo(activeContentResolver.value) 97 updateCollectionInfo(collectionInfos) 98 99 cachedCollectionInfo = getCachedCollectionInfo(provider) 100 } catch (e: RuntimeException) { 101 Log.e(TAG, "Could not refresh collection info cache", e) 102 } 103 } 104 105 return cachedCollectionInfo ?: CollectionInfo(provider.authority) 106 } 107 } 108