| /* |
| * Copyright (C) 2016 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.storagemanager.deletionhelper; |
| |
| import android.app.Activity; |
| import android.app.LoaderManager.LoaderCallbacks; |
| import android.content.Context; |
| import android.content.Loader; |
| import android.graphics.Bitmap; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import androidx.annotation.Nullable; |
| import android.util.ArraySet; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.storagemanager.deletionhelper.FetchDownloadsLoader.DownloadsResult; |
| |
| import java.io.File; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Set; |
| |
| /** |
| * The DownloadsDeletionType provides stale download file information to the |
| * {@link DownloadsDeletionPreferenceGroup}. |
| */ |
| public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<DownloadsResult> { |
| public static final String EXTRA_UNCHECKED_DOWNLOADS = "uncheckedFiles"; |
| private long mBytes; |
| private long mMostRecent; |
| private FreeableChangedListener mListener; |
| private Context mContext; |
| private ArraySet<File> mFiles; |
| private ArraySet<String> mUncheckedFiles; |
| private HashMap<File, Bitmap> mThumbnails; |
| private int mLoadingStatus; |
| |
| public DownloadsDeletionType(Context context, String[] uncheckedFiles) { |
| mLoadingStatus = LoadingStatus.LOADING; |
| mContext = context; |
| mFiles = new ArraySet<>(); |
| mUncheckedFiles = new ArraySet<>(); |
| if (uncheckedFiles != null) { |
| Collections.addAll(mUncheckedFiles, uncheckedFiles); |
| } |
| } |
| |
| @Override |
| public void registerFreeableChangedListener(FreeableChangedListener listener) { |
| mListener = listener; |
| if (mFiles != null) { |
| maybeUpdateListener(); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| } |
| |
| @Override |
| public void onPause() { |
| } |
| |
| @Override |
| public void onSaveInstanceStateBundle(Bundle savedInstanceState) { |
| savedInstanceState.putStringArray(EXTRA_UNCHECKED_DOWNLOADS, |
| mUncheckedFiles.toArray(new String[mUncheckedFiles.size()])); |
| } |
| |
| @Override |
| public void clearFreeableData(Activity activity) { |
| if (mFiles != null) { |
| AsyncTask.execute(new Runnable() { |
| @Override |
| public void run() { |
| boolean succeeded = true; |
| for (File entry : mFiles) { |
| if (isChecked(entry)) { |
| succeeded = succeeded && entry.delete(); |
| } |
| } |
| |
| if (!succeeded) { |
| MetricsLogger.action(activity, |
| MetricsEvent.ACTION_DELETION_HELPER_DOWNLOADS_DELETION_FAIL); |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public int getLoadingStatus() { |
| return mLoadingStatus; |
| } |
| |
| @Override |
| public int getContentCount() { |
| return mFiles.size(); |
| } |
| |
| @Override |
| public void setLoadingStatus(@LoadingStatus int loadingStatus) { |
| mLoadingStatus = loadingStatus; |
| } |
| |
| @Override |
| public Loader<DownloadsResult> onCreateLoader(int id, Bundle args) { |
| return new FetchDownloadsLoader(mContext, |
| Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<DownloadsResult> loader, DownloadsResult data) { |
| mMostRecent = data.youngestLastModified; |
| for (File file : data.files) { |
| mFiles.add(file); |
| } |
| mBytes = data.totalSize; |
| mThumbnails = data.thumbnails; |
| updateLoadingStatus(); |
| maybeUpdateListener(); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<DownloadsResult> loader) { |
| } |
| |
| /** |
| * Returns the most recent last modified time for any clearable file. |
| * @return The last modified time. |
| */ |
| public long getMostRecentLastModified() { |
| return mMostRecent; |
| } |
| |
| /** |
| * Returns the files in the Downloads folder after the loader task finishes. |
| */ |
| public Set<File> getFiles() { |
| if (mFiles == null) { |
| return null; |
| } |
| return mFiles; |
| } |
| |
| /** |
| * Set if a file should be deleted when the service is asked to clear files. |
| */ |
| public void setFileChecked(File file, boolean checked) { |
| if (checked) { |
| mUncheckedFiles.remove(file.getPath()); |
| } else { |
| mUncheckedFiles.add(file.getPath()); |
| } |
| } |
| |
| /** Returns the number of bytes that would be cleared if the deletion tasks runs. */ |
| public long getFreeableBytes(boolean countUnchecked) { |
| long freedBytes = 0; |
| for (File file : mFiles) { |
| if (isChecked(file) || countUnchecked) { |
| freedBytes += file.length(); |
| } |
| } |
| return freedBytes; |
| } |
| |
| /** Returns a thumbnail for a given file, if it exists. If it does not exist, returns null. */ |
| public @Nullable Bitmap getCachedThumbnail(File imageFile) { |
| if (mThumbnails == null) { |
| return null; |
| } |
| return mThumbnails.get(imageFile); |
| } |
| |
| /** |
| * Return if a given file is checked for deletion. |
| * |
| * @param file The file to check. |
| */ |
| public boolean isChecked(File file) { |
| return !mUncheckedFiles.contains(file.getPath()); |
| } |
| |
| private void maybeUpdateListener() { |
| if (mListener != null) { |
| mListener.onFreeableChanged(mFiles.size(), mBytes); |
| } |
| } |
| } |