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