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 androidx.annotation.VisibleForTesting; 21 import androidx.preference.Preference; 22 import androidx.preference.PreferenceScreen; 23 import android.text.format.Formatter; 24 import android.util.AttributeSet; 25 import com.android.internal.logging.MetricsLogger; 26 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 27 import com.android.storagemanager.R; 28 import com.android.storagemanager.utils.PreferenceListCache; 29 import java.util.List; 30 31 /** 32 * AppDeletionPreferenceGroup is a collapsible checkbox preference group which contains many 33 * apps to be cleared in the Deletion Helper. 34 */ 35 public class AppDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup 36 implements AppDeletionType.AppListener, Preference.OnPreferenceChangeListener { 37 private static final int ORDER_OFFSET = 100; 38 private AppDeletionType mBackend; 39 40 @VisibleForTesting PreferenceScreen mScreen; 41 AppDeletionPreferenceGroup(Context context)42 public AppDeletionPreferenceGroup(Context context) { 43 this(context, null); 44 } 45 AppDeletionPreferenceGroup(Context context, AttributeSet attrs)46 public AppDeletionPreferenceGroup(Context context, AttributeSet attrs) { 47 super(context, attrs); 48 setOnPreferenceChangeListener(this); 49 updateText(); 50 } 51 52 @Override onAppRebuild(List<AppsAsyncLoader.PackageInfo> apps)53 public void onAppRebuild(List<AppsAsyncLoader.PackageInfo> apps) { 54 int appCount = apps.size(); 55 int currentUserId = getContext().getUserId(); 56 PreferenceListCache cache = new PreferenceListCache(this); 57 for (int i = 0; i < appCount; i++) { 58 AppsAsyncLoader.PackageInfo app = apps.get(i); 59 60 if (app.userId != currentUserId) { 61 continue; 62 } 63 64 final String packageName = app.packageName; 65 AppDeletionPreference preference = 66 (AppDeletionPreference) cache.getCachedPreference(packageName); 67 if (preference == null) { 68 preference = new AppDeletionPreference(getContext(), app); 69 preference.setKey(packageName); 70 preference.setOnPreferenceChangeListener(this); 71 } 72 addThresholdDependentPreference(preference, isNoThreshold()); 73 preference.setChecked(mBackend.isChecked(packageName)); 74 preference.setOrder(i + ORDER_OFFSET); 75 preference.updateSummary(); 76 } 77 cache.removeCachedPrefs(); 78 updateText(); 79 } 80 addThresholdDependentPreference( AppDeletionPreference preference, boolean isThresholded)81 private void addThresholdDependentPreference( 82 AppDeletionPreference preference, boolean isThresholded) { 83 if (isNoThreshold()) { 84 addPreferenceToScreen(preference); 85 } else { 86 addPreference(preference); 87 } 88 } 89 isNoThreshold()90 private boolean isNoThreshold() { 91 return mBackend.getDeletionThreshold() == 0; 92 } 93 94 @VisibleForTesting addPreferenceToScreen(AppDeletionPreference preference)95 void addPreferenceToScreen(AppDeletionPreference preference) { 96 if (mScreen == null) { 97 mScreen = getPreferenceManager().getPreferenceScreen(); 98 } 99 mScreen.addPreference(preference); 100 } 101 102 @Override onPreferenceChange(Preference preference, Object newValue)103 public boolean onPreferenceChange(Preference preference, Object newValue) { 104 boolean isChecked = (boolean) newValue; 105 106 // If we have no AppDeletionType, we have no apps to toggle. 107 if (mBackend == null) { 108 return true; 109 } 110 111 if (preference == this) { 112 for (int i = 0; i < getPreferenceCount(); i++) { 113 AppDeletionPreference p = (AppDeletionPreference) getPreference(i); 114 p.setOnPreferenceChangeListener(null); 115 p.setChecked(isChecked); 116 mBackend.setChecked(p.getPackageName(), isChecked); 117 p.setOnPreferenceChangeListener(this); 118 } 119 updateText(); 120 MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_ALL_APPS, 121 isChecked); 122 return true; 123 } 124 125 // If a single preference changed, we need to toggle just itself. 126 AppDeletionPreference p = (AppDeletionPreference) preference; 127 mBackend.setChecked(p.getPackageName(), isChecked); 128 logAppToggle(isChecked, p.getPackageName()); 129 updateText(); 130 return true; 131 } 132 133 @Override onClick()134 public void onClick() { 135 super.onClick(); 136 MetricsLogger.action( 137 getContext(), MetricsEvent.ACTION_DELETION_APPS_COLLAPSED, isCollapsed()); 138 } 139 140 /** 141 * Initializes the PreferenceGroup with a source of apps to list. 142 * 143 * @param type The AppDeletionType which provides the app list. 144 */ setDeletionType(AppDeletionType type)145 public void setDeletionType(AppDeletionType type) { 146 mBackend = type; 147 } 148 updateText()149 private void updateText() { 150 long freeableBytes = 0; 151 long deletionThreshold = AppsAsyncLoader.UNUSED_DAYS_DELETION_THRESHOLD; 152 if (mBackend != null) { 153 freeableBytes = 154 mBackend.getTotalAppsFreeableSpace(DeletionHelperSettings.COUNT_UNCHECKED); 155 deletionThreshold = mBackend.getDeletionThreshold(); 156 switchSpinnerToCheckboxOrDisablePreference(freeableBytes, mBackend.getLoadingStatus()); 157 } 158 Context app = getContext(); 159 setTitle(app.getString(R.string.deletion_helper_apps_group_title)); 160 setSummary( 161 app.getString( 162 R.string.deletion_helper_apps_group_summary, 163 Formatter.formatFileSize(app, freeableBytes), 164 deletionThreshold)); 165 } 166 logAppToggle(boolean checked, String packageName)167 private void logAppToggle(boolean checked, String packageName) { 168 if (checked) { 169 MetricsLogger.action( 170 getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_ON, packageName); 171 } else { 172 MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_OFF, 173 packageName); 174 } 175 } 176 } 177