diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b5c071d2e9..333a1066e25 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7604,7 +7604,7 @@ Downloads (%1$d) - %1$s, last modified %2$s + %1$s • last modified %2$s %1$s diff --git a/res/xml/deletion_helper_list.xml b/res/xml/deletion_helper_list.xml index ac5a85159cc..64bd3b5974a 100644 --- a/res/xml/deletion_helper_list.xml +++ b/res/xml/deletion_helper_list.xml @@ -20,8 +20,9 @@ - + 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; } diff --git a/src/com/android/settings/deletionhelper/DownloadsDeletionPreference.java b/src/com/android/settings/deletionhelper/DownloadsDeletionPreference.java deleted file mode 100644 index 7cddf321a99..00000000000 --- a/src/com/android/settings/deletionhelper/DownloadsDeletionPreference.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.text.format.DateUtils; -import android.util.AttributeSet; -import android.text.format.Formatter; -import com.android.settings.DeletionPreference; -import com.android.settings.R; - -/** - * Preference to handle the deletion of photos and videos in the Deletion Helper. - */ -public class DownloadsDeletionPreference extends DeletionPreference { - public DownloadsDeletionPreference(Context context, AttributeSet attrs) { - super(context, attrs); - updatePreferenceText(0, 0, Long.MAX_VALUE); - } - - @Override - public void onFreeableChanged(int numItems, long freeableBytes) { - super.onFreeableChanged(numItems, freeableBytes); - DownloadsDeletionType deletionService = (DownloadsDeletionType) getDeletionService(); - updatePreferenceText(numItems, freeableBytes, deletionService.getMostRecentLastModified()); - } - - private void updatePreferenceText(int items, long bytes, long mostRecent) { - Context context = getContext(); - setTitle(context.getString(R.string.deletion_helper_downloads_title, - items)); - // If there are no files to clear, show the empty text instead. - if (mostRecent < Long.MAX_VALUE) { - 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))); - } - } - -} diff --git a/src/com/android/settings/deletionhelper/DownloadsDeletionPreferenceGroup.java b/src/com/android/settings/deletionhelper/DownloadsDeletionPreferenceGroup.java new file mode 100644 index 00000000000..440b96248df --- /dev/null +++ b/src/com/android/settings/deletionhelper/DownloadsDeletionPreferenceGroup.java @@ -0,0 +1,157 @@ +/* + * 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 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 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); + } + } +} diff --git a/src/com/android/settings/deletionhelper/DownloadsDeletionType.java b/src/com/android/settings/deletionhelper/DownloadsDeletionType.java index 81293d67a51..3a251eb3f79 100644 --- a/src/com/android/settings/deletionhelper/DownloadsDeletionType.java +++ b/src/com/android/settings/deletionhelper/DownloadsDeletionType.java @@ -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 { - private int mItems; private long mBytes; private long mMostRecent; private FreeableChangedListener mListener; - private FetchDownloadsLoader mTask; - private ArrayList mFiles; private Context mContext; + private ArrayMap mFiles; public DownloadsDeletionType(Context context) { mContext = context; + mFiles = new ArrayMap<>(); } @Override @@ -66,8 +70,10 @@ public class DownloadsDeletionType implements DeletionType, LoaderCallbacks entry : mFiles.entrySet()) { + if (entry.getValue()) { + entry.getKey().delete(); + } } } }); @@ -83,9 +89,13 @@ public class DownloadsDeletionType implements DeletionType, LoaderCallbacks 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 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 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); } } } diff --git a/src/com/android/settings/deletionhelper/DownloadsFilePreference.java b/src/com/android/settings/deletionhelper/DownloadsFilePreference.java new file mode 100644 index 00000000000..af8f6b6bae5 --- /dev/null +++ b/src/com/android/settings/deletionhelper/DownloadsFilePreference.java @@ -0,0 +1,68 @@ +/* + * 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.support.v7.preference.CheckBoxPreference; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import com.android.settings.R; + +import java.io.File; + +/** + * DownloadsFilePreference is a preference representing a file in the Downloads folder + * with a checkbox that represents if the file should be deleted. + */ +public class DownloadsFilePreference extends CheckBoxPreference { + private File mFile; + + public DownloadsFilePreference(Context context, File file) { + super(context); + mFile = file; + setKey(mFile.getPath()); + setTitle(file.getName()); + setSummary(context.getString(R.string.deletion_helper_downloads_summary, + Formatter.formatFileSize(getContext(), file.length()), + DateUtils.getRelativeTimeSpanString(mFile.lastModified(), + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE))); + } + + public File getFile() { + return mFile; + } + + @Override + public int compareTo(Preference other) { + if (other == null) { + return 1; + } + + if (other instanceof DownloadsFilePreference) { + DownloadsFilePreference preference = (DownloadsFilePreference) other; + return Long.compare(getFile().length(), preference.getFile().length()); + } else { + // If a non-DownloadsFilePreference appears, consider ourselves to be greater. + // This means if a non-DownloadsFilePreference sneaks into a DownloadsPreferenceGroup + // then the DownloadsFilePreference will appear higher. + return 1; + } + } +}