/* * Copyright (C) 2025 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.privatespace import android.content.ContentResolver import android.net.Uri import android.os.Environment import android.os.StatFs import androidx.annotation.VisibleForTesting import java.io.FileNotFoundException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext /** An interface declaring storage operations helper methods */ interface StorageHelper { suspend fun getAvailableDeviceStorage(): Long suspend fun calculateTotalSize( contentResolver: ContentResolver, uris: List, onError: (Uri, Exception) -> Unit, ): Long } /** Singleton implementation of {@link StorageHelper} */ object StorageHelperImpl : StorageHelper { private const val AVAILABLE_DEVICE_STORAGE_BUFFER: Long = 256_000_000L @VisibleForTesting var ioDispatcher: CoroutineDispatcher = Dispatchers.IO override suspend fun getAvailableDeviceStorage(): Long = withContext(ioDispatcher) { val file = Environment.getExternalStorageDirectory() val stat = StatFs(file.path) val availableBytes = stat.availableBlocksLong * stat.blockSizeLong (availableBytes - AVAILABLE_DEVICE_STORAGE_BUFFER) } override suspend fun calculateTotalSize( contentResolver: ContentResolver, uris: List, onError: (Uri, Exception) -> Unit, ): Long = withContext(ioDispatcher) { var totalBytes: Long = 0 for (fileUri in uris) { ensureActive() try { contentResolver.openFileDescriptor(fileUri, "r")?.use { fileDescriptor -> totalBytes += fileDescriptor.statSize } ?: run { onError( fileUri, IllegalStateException( "A null ParcelFileDescriptor was returned from ContentResolver" ), ) } } catch (e: FileNotFoundException) { onError(fileUri, e) } } totalBytes } }