1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.storagemanager.deletionhelper;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.drawable.BitmapDrawable;
23 import androidx.annotation.VisibleForTesting;
24 import androidx.preference.Preference;
25 import android.text.format.Formatter;
26 import android.util.AttributeSet;
27 
28 import com.android.internal.logging.MetricsLogger;
29 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
30 import com.android.storagemanager.R;
31 import com.android.storagemanager.utils.IconProvider;
32 import com.android.storagemanager.utils.PreferenceListCache;
33 
34 import java.io.File;
35 import java.util.Set;
36 
37 /**
38  * DownloadsDeletionPreferenceGroup defines a checkable preference group which contains
39  * downloads file deletion preferences.
40  */
41 public class DownloadsDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup
42         implements DeletionType.FreeableChangedListener, Preference.OnPreferenceChangeListener {
43     private DownloadsDeletionType mDeletionType;
44     private DeletionType.FreeableChangedListener mListener;
45     private IconProvider mIconProvider; // Purely for test.
46 
DownloadsDeletionPreferenceGroup(Context context)47     public DownloadsDeletionPreferenceGroup(Context context) {
48         super(context);
49         init();
50     }
51 
DownloadsDeletionPreferenceGroup(Context context, AttributeSet attrs)52     public DownloadsDeletionPreferenceGroup(Context context, AttributeSet attrs) {
53         super(context, attrs);
54         init();
55     }
56 
init()57     private void init() {
58         setChecked(true);
59         setOrderingAsAdded(false);
60         setOnPreferenceChangeListener(this);
61     }
62 
63     /**
64      * Set up a deletion type to get info for the preference group.
65      * @param type A {@link DownloadsDeletionType}.
66      */
registerDeletionService(DownloadsDeletionType type)67     public void registerDeletionService(DownloadsDeletionType type) {
68         mDeletionType = type;
69         mDeletionType.registerFreeableChangedListener(this);
70     }
71 
72     /**
73      * Registers a callback to be called when the amount of freeable space updates.
74      * @param listener The callback listener.
75      */
registerFreeableChangedListener(DeletionType.FreeableChangedListener listener)76     public void registerFreeableChangedListener(DeletionType.FreeableChangedListener listener) {
77         mListener = listener;
78     }
79 
80     @Override
onFreeableChanged(int numItems, long freeableBytes)81     public void onFreeableChanged(int numItems, long freeableBytes) {
82         updatePreferenceText(numItems, freeableBytes, mDeletionType.getMostRecentLastModified());
83         maybeUpdateListener(numItems, freeableBytes);
84         switchSpinnerToCheckboxOrDisablePreference(freeableBytes, mDeletionType.getLoadingStatus());
85         updateFiles();
86     }
87 
88     @Override
onPreferenceChange(Preference preference, Object newValue)89     public boolean onPreferenceChange(Preference preference, Object newValue) {
90         boolean checked = (boolean) newValue;
91         if (!checked) {
92             // Temporarily stop listening to avoid propagating the checked change to children.
93             setOnPreferenceChangeListener(null);
94             setChecked(false);
95             setOnPreferenceChangeListener(this);
96         }
97 
98         // If we have no deletion type, we have no files to toggle.
99         if (mDeletionType == null) {
100             return true;
101         }
102 
103         // If the group checkbox changed, we need to toggle every child preference.
104         if (preference == this) {
105             for (int i = 0; i < getPreferenceCount(); i++) {
106                 DownloadsFilePreference p = (DownloadsFilePreference) getPreference(i);
107                 p.setOnPreferenceChangeListener(null);
108                 mDeletionType.setFileChecked(p.getFile(), checked);
109                 p.setChecked(checked);
110                 p.setOnPreferenceChangeListener(this);
111             }
112             maybeUpdateListener(
113                     mDeletionType.getFiles().size(),
114                     mDeletionType.getFreeableBytes(DeletionHelperSettings.COUNT_CHECKED_ONLY));
115             MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_DOWNLOADS,
116                     checked);
117             return true;
118         }
119 
120         // If a single DownloadFilePreference changed, we need to toggle just itself.
121         DownloadsFilePreference p = (DownloadsFilePreference) preference;
122         mDeletionType.setFileChecked(p.getFile(), checked);
123         maybeUpdateListener(
124                 mDeletionType.getFiles().size(),
125                 mDeletionType.getFreeableBytes(DeletionHelperSettings.COUNT_CHECKED_ONLY));
126         return true;
127     }
128 
129     @Override
onClick()130     public void onClick() {
131         super.onClick();
132         MetricsLogger.action(
133                 getContext(), MetricsEvent.ACTION_DELETION_DOWNLOADS_COLLAPSED, isCollapsed());
134     }
135 
136     @VisibleForTesting
injectIconProvider(IconProvider iconProvider)137     void injectIconProvider(IconProvider iconProvider) {
138         mIconProvider = iconProvider;
139     }
140 
updatePreferenceText(int itemCount, long bytes, long mostRecent)141     private void updatePreferenceText(int itemCount, long bytes, long mostRecent) {
142         Context context = getContext();
143         setTitle(context.getString(R.string.deletion_helper_downloads_title));
144         // If there are no files to clear, show the empty text instead.
145         if (itemCount != 0) {
146             setSummary(context.getString(R.string.deletion_helper_downloads_category_summary,
147                     Formatter.formatFileSize(context, bytes)));
148         } else {
149             setSummary(context.getString(R.string.deletion_helper_downloads_summary_empty,
150                     Formatter.formatFileSize(context, bytes)));
151         }
152     }
153 
maybeUpdateListener(int numItems, long bytesFreeable)154     private void maybeUpdateListener(int numItems, long bytesFreeable) {
155         if (mListener != null) {
156             mListener.onFreeableChanged(numItems, bytesFreeable);
157         }
158     }
159 
updateFiles()160     private void updateFiles() {
161         PreferenceListCache cache = new PreferenceListCache(this);
162         Set<File> files = mDeletionType.getFiles();
163         Context context = getContext();
164         Resources res = context.getResources();
165         IconProvider iconProvider =
166                 mIconProvider == null ? new IconProvider(context) : mIconProvider;
167         for (File file : files) {
168             DownloadsFilePreference filePreference =
169                     (DownloadsFilePreference) cache.getCachedPreference(file.getPath());
170             if (filePreference == null) {
171                 filePreference = new DownloadsFilePreference(context, file, iconProvider);
172                 filePreference.setChecked(mDeletionType.isChecked(file));
173                 filePreference.setOnPreferenceChangeListener(this);
174                 Bitmap thumbnail = mDeletionType.getCachedThumbnail(file);
175                 if (thumbnail != null) {
176                     filePreference.setIcon(new BitmapDrawable(res, thumbnail));
177                 }
178             }
179             addPreference(filePreference);
180         }
181         cache.removeCachedPrefs();
182     }
183 }
184