Merge "Replace bluetooth pngs with vector drawables." into nyc-mr1-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4b5c071..333a106 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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>
diff --git a/res/xml/deletion_helper_list.xml b/res/xml/deletion_helper_list.xml
index ac5a851..64bd3b5 100644
--- a/res/xml/deletion_helper_list.xml
+++ b/res/xml/deletion_helper_list.xml
@@ -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"
diff --git a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java
index 68144f8..85c1035 100644
--- a/src/com/android/settings/deletionhelper/DeletionHelperFragment.java
+++ b/src/com/android/settings/deletionhelper/DeletionHelperFragment.java
@@ -25,10 +25,7 @@
 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 @@
     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 @@
         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 @@
         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 @@
         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 @@
         if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
             mPhotoVideoDeletion.clearFreeableData();
         }
+        mDownloadsDeletion.clearFreeableData();
 
         ArraySet<String> apps = new ArraySet<>();
         for (AppEntry entry : mAppEntries) {
@@ -351,9 +345,7 @@
         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 7cddf32..0000000
--- 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 0000000..440b962
--- /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<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);
+        }
+    }
+}
diff --git a/src/com/android/settings/deletionhelper/DownloadsDeletionType.java b/src/com/android/settings/deletionhelper/DownloadsDeletionType.java
index 81293d6..3a251eb 100644
--- a/src/com/android/settings/deletionhelper/DownloadsDeletionType.java
+++ b/src/com/android/settings/deletionhelper/DownloadsDeletionType.java
@@ -22,26 +22,30 @@
 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 @@
             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 @@
     @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 @@
         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);
         }
     }
 }
diff --git a/src/com/android/settings/deletionhelper/DownloadsFilePreference.java b/src/com/android/settings/deletionhelper/DownloadsFilePreference.java
new file mode 100644
index 0000000..af8f6b6
--- /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;
+        }
+    }
+}