Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1370f7ed authored by Daniel Nishi's avatar Daniel Nishi
Browse files

Change Downloads to show individual files in the Deletion Helper.

The first implementation simply had every file in the Downloads folder
be under a single checkbox. This iteration makes Downloads an
expandable dropdown preference listing every single file individually
for selection to be deleted.

Bug: 28621781
Change-Id: I5169caf718cee4c0fa7f0248bc4c443984766005
parent 97ac5c77
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -7604,7 +7604,7 @@
    <string name="deletion_helper_downloads_title">Downloads (<xliff:g id="numItems" example="67">%1$d</xliff:g>)</string>
    <!-- Summary of how much stale data can be cleared from the local download folder. [CHAR LIMIT=NONE]-->
    <string name="deletion_helper_downloads_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g>, last modified <xliff:g id="days">%2$s</xliff:g></string>
    <string name="deletion_helper_downloads_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> last modified <xliff:g id="days">%2$s</xliff:g></string>
    <!-- Summary for when when there is nothing in the downloads folder to clear. [CHAR LIMIT=NONE]-->
    <string name="deletion_helper_downloads_summary_empty"><xliff:g id="used" example="1.2GB">%1$s</xliff:g></string>
+3 −2
Original line number Diff line number Diff line
@@ -20,8 +20,9 @@
    <com.android.settings.PhotosDeletionPreference
        android:key="delete_photos" />

    <com.android.settings.deletionhelper.DownloadsDeletionPreference
        android:key="delete_downloads" />
    <com.android.settings.deletionhelper.DownloadsDeletionPreferenceGroup
        android:key="delete_downloads"
        android:icon="@drawable/ic_keyboard_arrow_down_black_32"/>

    <com.android.settings.CollapsibleCheckboxPreferenceGroup
            android:key="apps_group"
+7 −15
Original line number Diff line number Diff line
@@ -25,10 +25,7 @@ import android.text.format.Formatter;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import com.android.settings.deletionhelper.DownloadsDeletionPreference;
import com.android.settings.CollapsibleCheckboxPreferenceGroup;
import com.android.settings.PhotosDeletionPreference;
import com.android.settings.SettingsPreferenceFragment;
@@ -70,7 +67,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
    private Button mCancel, mFree;
    private CollapsibleCheckboxPreferenceGroup mApps;
    private PhotosDeletionPreference mPhotoPreference;
    private DownloadsDeletionPreference mDownloadsPreference;
    private DownloadsDeletionPreferenceGroup mDownloadsPreference;

    private ApplicationsState mState;
    private Session mSession;
@@ -96,7 +93,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
        mApps = (CollapsibleCheckboxPreferenceGroup) findPreference(KEY_APPS_GROUP);
        mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE);
        mDownloadsPreference =
                (DownloadsDeletionPreference) findPreference(KEY_DOWNLOADS_PREFERENCE);
                (DownloadsDeletionPreferenceGroup) findPreference(KEY_DOWNLOADS_PREFERENCE);
        mProvider =
                FeatureFactory.getFactory(app).getDeletionHelperFeatureProvider();
        if (mProvider != null) {
@@ -155,14 +152,12 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
        super.onResume();
        mSession.resume();
        mDataUsageBridge.resume();
        mDownloadsDeletion.onResume();
        getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);

        if (mPhotoVideoDeletion != null) {
            mPhotoVideoDeletion.onResume();
        }
        if (mDownloadsDeletion != null) {
            mDownloadsDeletion.onResume();
            getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);
        }
    }


@@ -180,13 +175,11 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
        super.onPause();
        mDataUsageBridge.pause();
        mSession.pause();
        mDownloadsDeletion.onPause();

        if (mPhotoVideoDeletion != null) {
            mPhotoVideoDeletion.onPause();
        }
        if (mDownloadsDeletion != null) {
            mDownloadsDeletion.onPause();
        }
    }

    private void rebuild() {
@@ -316,6 +309,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
        if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
            mPhotoVideoDeletion.clearFreeableData();
        }
        mDownloadsDeletion.clearFreeableData();

        ArraySet<String> apps = new ArraySet<>();
        for (AppEntry entry : mAppEntries) {
@@ -351,9 +345,7 @@ public class DeletionHelperFragment extends SettingsPreferenceFragment implement
        if (mPhotoPreference != null) {
            freeableSpace += mPhotoPreference.getFreeableBytes();
        }
        if (mDownloadsPreference != null) {
            freeableSpace += mDownloadsPreference.getFreeableBytes();
        }
        freeableSpace += mDownloadsDeletion.getFreeableBytes();
        return freeableSpace;
    }

+157 −0
Original line number Diff line number Diff line
/*
 * 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.settings.deletionhelper;

import android.content.Context;
import android.support.v7.preference.Preference;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.AttributeSet;
import com.android.settings.CollapsibleCheckboxPreferenceGroup;
import com.android.settings.R;

import java.io.File;
import java.util.Set;

/**
 * DownloadsDeletionPreferenceGroup defines a checkable preference group which contains
 * downloads file deletion preferences.
 */
public class DownloadsDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup
        implements DeletionType.FreeableChangedListener, Preference.OnPreferenceChangeListener {
    private DownloadsDeletionType mDeletionType;
    private DeletionType.FreeableChangedListener mListener;

    public DownloadsDeletionPreferenceGroup(Context context) {
        this(context, null);
    }

    public DownloadsDeletionPreferenceGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrderingAsAdded(false);
        setOnPreferenceChangeListener(this);
    }

    /**
     * Set up a deletion type to get info for the preference group.
     * @param type A {@link DownloadsDeletionType}.
     */
    public void registerDeletionService(DownloadsDeletionType type) {
        mDeletionType = type;
        mDeletionType.registerFreeableChangedListener(this);
    }

    /**
     * Registers a callback to be called when the amount of freeable space updates.
     * @param listener The callback listener.
     */
    public void registerFreeableChangedListener(DeletionType.FreeableChangedListener listener) {
        mListener = listener;
    }

    @Override
    public void onFreeableChanged(int numItems, long freeableBytes) {
        updatePreferenceText(numItems, freeableBytes, mDeletionType.getMostRecentLastModified());
        maybeUpdateListener(numItems, freeableBytes);
        updateFiles();
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        boolean checked = (boolean) newValue;
        if (!checked) {
            // Temporarily stop listening to avoid propagating the checked change to children.
            setOnPreferenceChangeListener(null);
            setChecked(false);
            setOnPreferenceChangeListener(this);
        }

        // If the group checkbox changed, we need to toggle every child preference.
        if (preference == this) {
            for (int i = 0; i < getPreferenceCount(); i++) {
                DownloadsFilePreference p = (DownloadsFilePreference) getPreference(i);
                p.setOnPreferenceChangeListener(null);
                mDeletionType.toggleFile(p.getFile(), checked);
                p.setChecked(checked);
                p.setOnPreferenceChangeListener(this);
            }
            maybeUpdateListener(mDeletionType.getFiles().size(), mDeletionType.getFreeableBytes());
            return true;
        }

        // If a single DownloadFilePreference changed, we need to toggle just itself.
        DownloadsFilePreference p = (DownloadsFilePreference) preference;
        mDeletionType.toggleFile(p.getFile(), checked);
        maybeUpdateListener(mDeletionType.getFiles().size(), mDeletionType.getFreeableBytes());
        return true;
    }


    private void updatePreferenceText(int itemCount, long bytes, long mostRecent) {
        Context context = getContext();
        setTitle(context.getString(R.string.deletion_helper_downloads_title, itemCount));
        // If there are no files to clear, show the empty text instead.
        if (itemCount != 0) {
            setSummary(context.getString(R.string.deletion_helper_downloads_summary,
                    Formatter.formatFileSize(context, bytes),
                    DateUtils.getRelativeTimeSpanString(mostRecent,
                            System.currentTimeMillis(),
                            DateUtils.DAY_IN_MILLIS,
                            DateUtils.FORMAT_ABBREV_RELATIVE)));
        } else {
            setSummary(context.getString(R.string.deletion_helper_downloads_summary_empty,
                    Formatter.formatFileSize(context, bytes)));
        }
    }

    private void maybeUpdateListener(int numItems, long bytesFreeable) {
        if (mListener != null) {
            mListener.onFreeableChanged(numItems, bytesFreeable);
        }
    }

    private void updateFiles() {
        // TODO: Remove impl overlap with the cached preferences methods in
        // SettingsPreferenceFragment.

        // Cache the existing file preferences.
        ArrayMap<String, Preference> cachedPreferences = new ArrayMap<>();
        for (int i = 0; i < getPreferenceCount(); i++) {
            Preference p = getPreference(i);
            cachedPreferences.put(p.getKey(), p);
        }

        // Iterate over all of the files and re-use the old file preference, if it exists.
        Set<File> files = mDeletionType.getFiles();
        for (File file : files) {
            DownloadsFilePreference filePreference =
                    (DownloadsFilePreference) cachedPreferences.remove(file.getPath());
            if (filePreference == null) {
                filePreference = new DownloadsFilePreference(getContext(), file);
                filePreference.setChecked(isChecked());
                filePreference.setOnPreferenceChangeListener(this);
            }
            addPreference(filePreference);
        }

        // Remove all of the unused preferences.
        for (Preference p : cachedPreferences.values()) {
            removePreference(p);
        }
    }
}
+49 −9
Original line number Diff line number Diff line
@@ -22,26 +22,30 @@ import android.content.Loader;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.settings.deletionhelper.FetchDownloadsLoader.DownloadsResult;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The DownloadsDeletionType provides stale download file information to the
 * {@link DownloadsDeletionPreference}.
 * {@link DownloadsDeletionPreferenceGroup}.
 */
public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<DownloadsResult> {
    private int mItems;
    private long mBytes;
    private long mMostRecent;
    private FreeableChangedListener mListener;
    private FetchDownloadsLoader mTask;
    private ArrayList<File> mFiles;
    private Context mContext;
    private ArrayMap<File, Boolean> mFiles;

    public DownloadsDeletionType(Context context) {
        mContext = context;
        mFiles = new ArrayMap<>();
    }

    @Override
@@ -66,8 +70,10 @@ public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<Down
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    for (File file : mFiles) {
                        file.delete();
                    for (Map.Entry<File, Boolean> entry : mFiles.entrySet()) {
                        if (entry.getValue()) {
                            entry.getKey().delete();
                        }
                    }
                }
            });
@@ -83,9 +89,13 @@ public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<Down
    @Override
    public void onLoadFinished(Loader<DownloadsResult> loader, DownloadsResult data) {
        mMostRecent = data.youngestLastModified;
        mFiles = data.files;
        for (File file : data.files) {
            if (mFiles.containsKey(file)) {
                continue;
            }
            mFiles.put(file, false);
        }
        mBytes = data.totalSize;
        mItems = mFiles.size();
        maybeUpdateListener();
    }

@@ -101,9 +111,39 @@ public class DownloadsDeletionType implements DeletionType, LoaderCallbacks<Down
        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.keySet();
    }

    /**
     * Toggle if a file should be deleted when the service is asked to clear files.
     */
    public void toggleFile(File file, boolean checked) {
        mFiles.put(file, checked);
    }

    /**
     * Returns the number of bytes that would be cleared if the deletion tasks runs.
     */
    public long getFreeableBytes() {
        long freedBytes = 0;
        for (Map.Entry<File, Boolean> entry : mFiles.entrySet()) {
            if (entry.getValue()) {
                freedBytes += entry.getKey().length();
            }
        }
        return freedBytes;
    }

    private void maybeUpdateListener() {
        if (mListener != null) {
            mListener.onFreeableChanged(mItems, mBytes);
            mListener.onFreeableChanged(mFiles.size(), mBytes);
        }
    }
}
Loading