/* * 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.lemenzo.files import android.os.Build import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.DocumentsContract import android.provider.Flags import androidx.test.filters.MediumTest import androidx.test.filters.SdkSuppress import com.lemenzo.files.base.State import com.lemenzo.files.base.UserId import com.lemenzo.files.loaders.TrashFileLoader import com.lemenzo.files.testing.ActivityManagers import com.lemenzo.files.testing.TestEnv import com.lemenzo.files.testing.TestFileTypeLookup import com.lemenzo.files.testing.TestImmediateExecutor import com.lemenzo.files.testing.TestProvidersAccess import com.lemenzo.files.testing.UserManagers import com.lemenzo.files.util.VersionUtils import com.android.modules.utils.build.SdkLevel import com.google.common.collect.Lists import junit.framework.Assert import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.`when` as whenever @RunWith(Parameterized::class) @MediumTest @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DOCUMENTS_TRASH_API) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "B") internal class TrashFileLoaderTest { private lateinit var mEnv: TestEnv private lateinit var mActivity: TestActivity private lateinit var mTestConfigStore: TestConfigStore @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @JvmField @Parameterized.Parameter(0) var isPrivateSpaceEnabled: Boolean = false @Before fun setUp() { // Skip test if the platform SDK is not newer than Android Baklava (SDK 36). // The Trash feature under test relies on DocumentsContract APIs introduced in the // Android release after Baklava (SDK 36). // As LemenzoFiles is a Mainline module, it's subject to MTS testing, which runs on // older Android base builds to verify backward compatibility. However, this specific // Trash feature lacks backward compatibility with platforms at or below Baklava. // This assumption prevents failures when the test runs on an older base OS // without the necessary APIs. assumeTrue(VersionUtils.isGreaterThanB()) mEnv = TestEnv.create() mActivity = TestActivity.create(mEnv) mActivity.activityManager = ActivityManagers.create(false) mActivity.userManager = UserManagers.create() mTestConfigStore = TestConfigStore() mEnv.state.configStore = mTestConfigStore mEnv.state.action = State.ACTION_BROWSE mEnv.state.acceptMimes = arrayOf("*/*") isPrivateSpaceEnabled = SdkLevel.isAtLeastS() && isPrivateSpaceEnabled if (isPrivateSpaceEnabled) { mTestConfigStore.enablePrivateSpaceInPhotoPicker() mEnv.state.canForwardToProfileIdMap.put(UserId.DEFAULT_USER, true) mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.OtherUser.USER_ID, true) } else { mEnv.state.canShareAcrossProfile = true } } /** Tests that the loader correctly fetches trashed documents. */ @Test fun testFetchTrashFiles() { val doc1 = mEnv.model.createFile("test1") val doc2 = mEnv.model.createFile("test2") val folder1 = mEnv.model.createFolder("folder1") mEnv.mockProviders .get(TestProvidersAccess.HOME.authority)!! .setNextTrashDocumentsReturns(doc1, doc2, folder1) val loader = createTrashFileLoader() val result = loader.loadInBackground()!! Assert.assertEquals(3, result.cursor.getCount()) } /** * Verifies that the loader correctly returns hidden files (those with a '.' prefix) from the * trash, regardless of the 'show hidden files' setting. */ @Test fun testShowHiddenFilesInTrash() { val doc1 = mEnv.model.createFile(".test") val doc2 = mEnv.model.createFile("test") doc1.documentId = ".test" doc2.documentId = "parent_folder/.hidden_folder/test" doc1.lastModified = System.currentTimeMillis() doc2.lastModified = System.currentTimeMillis() mEnv.mockProviders .get(TestProvidersAccess.HOME.authority)!! .setNextTrashDocumentsReturns(doc1, doc2) val loader = createTrashFileLoader() Assert.assertFalse(loader.mState.showHiddenFiles) var result = loader.loadInBackground()!! Assert.assertEquals(2, result.cursor.getCount()) loader.mState.showHiddenFiles = true result = loader.loadInBackground()!! Assert.assertEquals(2, result.cursor.getCount()) } /** * Asserts that documents in the trash do not support 'move' or 'remove' operations, only * 'delete', by checking the document flags returned by the loader. */ @Test fun testTrashDocumentsNotRenameAndMove() { val doc = mEnv.model.createFile( "freddy.jpg", (DocumentsContract.Document.FLAG_SUPPORTS_MOVE or DocumentsContract.Document.FLAG_SUPPORTS_DELETE or DocumentsContract.Document.FLAG_SUPPORTS_REMOVE), ) mEnv.mockProviders .get(TestProvidersAccess.HOME.authority)!! .setNextTrashDocumentsReturns(doc) val loader = createTrashFileLoader() val result = loader.loadInBackground()!! val cursor = result.cursor Assert.assertEquals(1, cursor.getCount()) // Iterate through the cursor to verify that the returned document has the correct flags. // The document should support deletion but not moving or removing. for (i in 0.. { return Lists.newArrayList(true, false) } } }