/* * 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.documentsui.loaders import android.content.Context import android.database.Cursor import android.database.CursorWrapper import android.net.Uri import android.provider.DocumentsContract import com.android.documentsui.CrossProfileNoPermissionException import com.android.documentsui.CrossProfileQuietModeException import com.android.documentsui.DirectoryResult import com.android.documentsui.MultiRootDocumentsLoader import com.android.documentsui.base.Lookup import com.android.documentsui.base.RootInfo import com.android.documentsui.base.State import com.android.documentsui.base.UserId import com.android.documentsui.roots.ProvidersAccess import com.android.documentsui.roots.RootCursorWrapper import java.util.concurrent.Executor /** * A loader that queries and merges documents from the trash across multiple providers for a * specific user. * *
This loader handles cross-profile permission and quiet mode checks before loading. It ensures
* that each content provider authority is queried only once, even if multiple roots from the same
* provider are present. It uses a custom cursor to modify the flags of trashed items, preventing
* standard move and remove operations.
*/
class TrashFileLoader(
context: Context?,
providers: ProvidersAccess?,
state: State?,
executors: Lookup Before loading, this method checks if interaction with the specified user is permitted and
* if the user's profile is in quiet mode. If either of these conditions is not met, it returns
* a {@link DirectoryResult} with the appropriate exception.
*
* @return A {@link DirectoryResult} containing the merged cursor of trashed documents or an
* exception if loading failed.
*/
override fun loadInBackground(): DirectoryResult? {
if (!mState.canInteractWith(mUserId)) {
val result = DirectoryResult()
result.exception = CrossProfileNoPermissionException()
return result
} else if (mUserId.isQuietModeEnabled(getContext())) {
val result = DirectoryResult()
result.exception = CrossProfileQuietModeException(mUserId)
return result
}
return super.loadInBackground()
}
/**
* Determines whether a root should be ignored to avoid redundant queries for trashed documents.
*
* The trash content is fetched per-authority using {@link
* android.provider.DocumentsContract#buildTrashDocumentsUri(String)}. If a single authority
* provides multiple roots, querying each would result in the same set of trashed documents
* being returned, leading to duplicate entries. This method prevents this by ensuring that each
* authority is queried only once.
*
* @param root The {@link RootInfo} to check.
* @return {@code true} if the root's authority is null or has already been queried, {@code
* false} otherwise.
*/
public override fun shouldIgnoreRoot(root: RootInfo): Boolean {
// A single provider may have multiple roots, but trashed documents are queried
// at the authority level. We track called authorities to avoid duplicate queries.
if (root.authority == null || mCalledAuthoritiesSet.contains(root.authority)) {
return true
}
mCalledAuthoritiesSet.add(root.authority)
return false
}
/**
* Returns a {@link QueryTask} specifically for querying trashed documents.
*
* @param authority The authority of the content provider to query.
* @param rootInfos The list of roots associated with the authority.
* @return A new {@link TrashTask} instance.
*/
override fun getQueryTask(authority: String, rootInfos: List