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