109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi/* 209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * Copyright (C) 2016 The Android Open Source Project 309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * 409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * Licensed under the Apache License, Version 2.0 (the "License"); 509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * you may not use this file except in compliance with the License. 609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * You may obtain a copy of the License at 709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * 809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * http://www.apache.org/licenses/LICENSE-2.0 909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * 1009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * Unless required by applicable law or agreed to in writing, software 1109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * distributed under the License is distributed on an "AS IS" BASIS, 1209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * See the License for the specific language governing permissions and 1409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * limitations under the License. 1509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi */ 1609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 1709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishipackage com.android.storagemanager.deletionhelper; 1809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 1909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport android.content.Context; 2033d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinezimport android.support.annotation.VisibleForTesting; 2109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport android.support.v7.preference.Preference; 2233d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinezimport android.support.v7.preference.PreferenceScreen; 2309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport android.text.format.Formatter; 2409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport android.util.AttributeSet; 252ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishiimport com.android.internal.logging.MetricsLogger; 261efe581749de7f50de41bac1e31b1389ac964615Tamas Berghammerimport com.android.internal.logging.nano.MetricsProto.MetricsEvent; 2709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport com.android.storagemanager.R; 28dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinezimport com.android.storagemanager.utils.PreferenceListCache; 2909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishiimport java.util.List; 3009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 3109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi/** 3209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * AppDeletionPreferenceGroup is a collapsible checkbox preference group which contains many 3309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * apps to be cleared in the Deletion Helper. 3409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi */ 3509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishipublic class AppDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup 3609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi implements AppDeletionType.AppListener, Preference.OnPreferenceChangeListener { 37dc0723e2d95967060885c3266d2c3b09aa3319a1Daniel Nishi private static final int ORDER_OFFSET = 100; 3809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi private AppDeletionType mBackend; 3909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 4033d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez @VisibleForTesting PreferenceScreen mScreen; 4133d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez 4209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi public AppDeletionPreferenceGroup(Context context) { 4309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi this(context, null); 4409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 4509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 4609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi public AppDeletionPreferenceGroup(Context context, AttributeSet attrs) { 4709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi super(context, attrs); 4809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi setOnPreferenceChangeListener(this); 4946afa46254906b9ba370841527066f8c5c58adb4Daniel Nishi updateText(); 5009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 5109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 5209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi @Override 53dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez public void onAppRebuild(List<AppsAsyncLoader.PackageInfo> apps) { 54dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez int appCount = apps.size(); 55cd65f0119e6ef2e282e19b200c07ae42ff9efe5fDaniel Nishi int currentUserId = getContext().getUserId(); 5609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi PreferenceListCache cache = new PreferenceListCache(this); 57dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez for (int i = 0; i < appCount; i++) { 58dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez AppsAsyncLoader.PackageInfo app = apps.get(i); 59cd65f0119e6ef2e282e19b200c07ae42ff9efe5fDaniel Nishi 60dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez if (app.userId != currentUserId) { 61cd65f0119e6ef2e282e19b200c07ae42ff9efe5fDaniel Nishi continue; 62cd65f0119e6ef2e282e19b200c07ae42ff9efe5fDaniel Nishi } 63cd65f0119e6ef2e282e19b200c07ae42ff9efe5fDaniel Nishi 64dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez final String packageName = app.packageName; 6509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi AppDeletionPreference preference = 6609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi (AppDeletionPreference) cache.getCachedPreference(packageName); 6709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi if (preference == null) { 68dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez preference = new AppDeletionPreference(getContext(), app); 6909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi preference.setKey(packageName); 7009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi preference.setOnPreferenceChangeListener(this); 7109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 7233d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez addThresholdDependentPreference(preference, isNoThreshold()); 7309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi preference.setChecked(mBackend.isChecked(packageName)); 74dc0723e2d95967060885c3266d2c3b09aa3319a1Daniel Nishi preference.setOrder(i + ORDER_OFFSET); 755e2460bb808460c37e9409e0735a95998457647cDaniel Nishi preference.updateSummary(); 7609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 7709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi cache.removeCachedPrefs(); 7809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi updateText(); 7909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 8009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 8133d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez private void addThresholdDependentPreference( 8233d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez AppDeletionPreference preference, boolean isThresholded) { 8333d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez if (isNoThreshold()) { 8433d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez addPreferenceToScreen(preference); 8533d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } else { 8633d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez addPreference(preference); 8733d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } 8833d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } 8933d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez 9033d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez private boolean isNoThreshold() { 9133d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez return mBackend.getDeletionThreshold() == 0; 9233d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } 9333d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez 9433d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez @VisibleForTesting 9533d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez void addPreferenceToScreen(AppDeletionPreference preference) { 9633d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez if (mScreen == null) { 9733d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez mScreen = getPreferenceManager().getPreferenceScreen(); 9833d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } 9933d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez mScreen.addPreference(preference); 10033d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez } 10133d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez 10209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi @Override 10309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi public boolean onPreferenceChange(Preference preference, Object newValue) { 10409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi boolean isChecked = (boolean) newValue; 105c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi 106c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi // If we have no AppDeletionType, we have no apps to toggle. 107c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi if (mBackend == null) { 108c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi return true; 109c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi } 110c950058526c77f9e6a2cb3635ceb0d6b55fab45fDaniel Nishi 11109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi if (preference == this) { 11209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi for (int i = 0; i < getPreferenceCount(); i++) { 11309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi AppDeletionPreference p = (AppDeletionPreference) getPreference(i); 11409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi p.setOnPreferenceChangeListener(null); 11509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi p.setChecked(isChecked); 11609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi mBackend.setChecked(p.getPackageName(), isChecked); 11709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi p.setOnPreferenceChangeListener(this); 11809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 11909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi updateText(); 1202ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_ALL_APPS, 1212ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi isChecked); 12209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi return true; 12309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 12409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 12509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi // If a single preference changed, we need to toggle just itself. 12609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi AppDeletionPreference p = (AppDeletionPreference) preference; 12709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi mBackend.setChecked(p.getPackageName(), isChecked); 1282ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi logAppToggle(isChecked, p.getPackageName()); 12909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi updateText(); 13009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi return true; 13109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 13209fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 1332ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi @Override 1342ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi public void onClick() { 1352ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi super.onClick(); 13633d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez MetricsLogger.action( 13733d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez getContext(), MetricsEvent.ACTION_DELETION_APPS_COLLAPSED, isCollapsed()); 1382ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi } 1392ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi 14009fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi /** 14109fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * Initializes the PreferenceGroup with a source of apps to list. 14233d4cb4fd65e6846e0db80abd8b4ae3f242c04acSalvador Martinez * 14309fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi * @param type The AppDeletionType which provides the app list. 14409fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi */ 14509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi public void setDeletionType(AppDeletionType type) { 14609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi mBackend = type; 14709fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 14809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi 14909fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi private void updateText() { 15046afa46254906b9ba370841527066f8c5c58adb4Daniel Nishi long freeableBytes = 0; 151dee882c960b4db9bae9f2c9144b84c6ba001a051Salvador Martinez long deletionThreshold = AppsAsyncLoader.UNUSED_DAYS_DELETION_THRESHOLD; 15246afa46254906b9ba370841527066f8c5c58adb4Daniel Nishi if (mBackend != null) { 153a077e8d3f8c99fd3769d93c4094d434115072a9cSalvador Martinez freeableBytes = 154a077e8d3f8c99fd3769d93c4094d434115072a9cSalvador Martinez mBackend.getTotalAppsFreeableSpace(DeletionHelperSettings.COUNT_UNCHECKED); 1559385b673317060523b66a40780d5ad1df1530a56Salvador Martinez deletionThreshold = mBackend.getDeletionThreshold(); 156828974f55fa2aae46d8137747e284a7861d8086cSalvador Martinez switchSpinnerToCheckboxOrDisablePreference(freeableBytes, mBackend.getLoadingStatus()); 15746afa46254906b9ba370841527066f8c5c58adb4Daniel Nishi } 15809fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi Context app = getContext(); 1590b30611cd33805255a7538cc16cf203a761b6978Daniel Nishi setTitle(app.getString(R.string.deletion_helper_apps_group_title)); 1609385b673317060523b66a40780d5ad1df1530a56Salvador Martinez setSummary( 1619385b673317060523b66a40780d5ad1df1530a56Salvador Martinez app.getString( 1629385b673317060523b66a40780d5ad1df1530a56Salvador Martinez R.string.deletion_helper_apps_group_summary, 1639385b673317060523b66a40780d5ad1df1530a56Salvador Martinez Formatter.formatFileSize(app, freeableBytes), 1649385b673317060523b66a40780d5ad1df1530a56Salvador Martinez deletionThreshold)); 16509fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi } 1662ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi 1672ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi private void logAppToggle(boolean checked, String packageName) { 1682ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi if (checked) { 169a077e8d3f8c99fd3769d93c4094d434115072a9cSalvador Martinez MetricsLogger.action( 170a077e8d3f8c99fd3769d93c4094d434115072a9cSalvador Martinez getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_ON, packageName); 1712ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi } else { 1722ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_OFF, 1732ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi packageName); 1742ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi } 1752ace931586ac5ee3d0633c051bc276690975f3eaDaniel Nishi } 17609fa1388d090666e631f9d764f935604a2c753e9Daniel Nishi} 177