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