/* * 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.integration import android.content.ContentValues import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.os.Environment import android.os.UserHandle import android.provider.MediaStore import android.util.Log import com.google.common.truth.Truth.assertWithMessage /** Helper class to manage the lifecycle of test files. */ class TestFilesHelper(private val context: Context, private val userForFileOps: UserHandle) { companion object { private const val TAG: String = "TestFilesHelper" private const val TEST_IMAGE_NAME_PREFIX: String = "test_image_" } private val createdFiles = mutableListOf() /** Creates [count] number of test image files and returns their info. */ fun create(count: Int): List { val newFiles = mutableListOf() repeat(count) { val displayName = TEST_IMAGE_NAME_PREFIX + System.currentTimeMillis() val fileInfo = savePngDrawableToMediaStore(R.drawable.android, displayName) assertWithMessage("Unable to create test file with name '$displayName'") .that(fileInfo) .isNotNull() newFiles.add(fileInfo!!) } createdFiles.addAll(newFiles) return newFiles } /** Deletes all files that were created by this helper instance. */ fun cleanup() { createdFiles.forEach { removeOriginalFile(it.uri) } createdFiles.clear() } private fun savePngDrawableToMediaStore(drawableResId: Int, displayName: String): FileInfo? { val bitmap = BitmapFactory.decodeResource(context.resources, drawableResId) val resolver = context.contentResolver val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "$displayName.png") put(MediaStore.Images.Media.MIME_TYPE, "image/png") put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) put(MediaStore.Images.Media.IS_PENDING, 1) } val imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) imageUri?.let { uri -> resolver.openOutputStream(uri)?.use { outputStream -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) } contentValues.clear() contentValues.put(MediaStore.Images.Media.IS_PENDING, 0) resolver.update(uri, contentValues, null, null) } if (imageUri == null) { return null } return FileInfo(uriWithUserId(imageUri, userForFileOps.identifier), "$displayName.png") } private fun uriWithUserId(uri: Uri, userId: Int): Uri { val builder = uri.buildUpon() builder.encodedAuthority("" + userId + "@" + uri.getEncodedAuthority()) return builder.build() } fun originalFileExists(uri: Uri): Boolean { return try { context.contentResolver.openFileDescriptor(uri, "r")?.use { true } ?: false } catch (e: Exception) { Log.e(TAG, "Unable to open uri: $uri", e) false } } private fun removeOriginalFile(uri: Uri?) { if (uri == null) { return } try { context.contentResolver.delete(uri, Bundle()) } catch (e: Exception) { Log.e(TAG, "File already removed or unavailable: $uri", e) } } }