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
17package com.android.storagemanager.deletionhelper;
18
19import android.content.Context;
20import android.support.annotation.VisibleForTesting;
21import android.support.v7.preference.Preference;
22import android.support.v7.preference.PreferenceScreen;
23import android.text.format.Formatter;
24import android.util.AttributeSet;
25import com.android.internal.logging.MetricsLogger;
26import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
27import com.android.storagemanager.R;
28import com.android.storagemanager.utils.PreferenceListCache;
29import 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 */
35public 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
42    public AppDeletionPreferenceGroup(Context context) {
43        this(context, null);
44    }
45
46    public AppDeletionPreferenceGroup(Context context, AttributeSet attrs) {
47        super(context, attrs);
48        setOnPreferenceChangeListener(this);
49        updateText();
50    }
51
52    @Override
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
81    private void addThresholdDependentPreference(
82            AppDeletionPreference preference, boolean isThresholded) {
83        if (isNoThreshold()) {
84            addPreferenceToScreen(preference);
85        } else {
86            addPreference(preference);
87        }
88    }
89
90    private boolean isNoThreshold() {
91        return mBackend.getDeletionThreshold() == 0;
92    }
93
94    @VisibleForTesting
95    void addPreferenceToScreen(AppDeletionPreference preference) {
96        if (mScreen == null) {
97            mScreen = getPreferenceManager().getPreferenceScreen();
98        }
99        mScreen.addPreference(preference);
100    }
101
102    @Override
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
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     */
145    public void setDeletionType(AppDeletionType type) {
146        mBackend = type;
147    }
148
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
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