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.v7.preference.Preference;
21import android.text.format.Formatter;
22import android.util.AttributeSet;
23import android.view.View;
24import com.android.internal.logging.MetricsLogger;
25import com.android.internal.logging.MetricsProto.MetricsEvent;
26import com.android.settingslib.applications.ApplicationsState;
27import com.android.storagemanager.deletionhelper.AppStateUsageStatsBridge.UsageStatsState;
28import com.android.storagemanager.PreferenceListCache;
29import com.android.storagemanager.R;
30
31import java.util.List;
32
33/**
34 * AppDeletionPreferenceGroup is a collapsible checkbox preference group which contains many
35 * apps to be cleared in the Deletion Helper.
36 */
37public class AppDeletionPreferenceGroup extends CollapsibleCheckboxPreferenceGroup
38        implements AppDeletionType.AppListener, Preference.OnPreferenceChangeListener {
39    private AppDeletionType mBackend;
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<ApplicationsState.AppEntry> apps) {
53        int entryCount = apps.size();
54        int currentUserId = getContext().getUserId();
55        PreferenceListCache cache = new PreferenceListCache(this);
56        for (int i = 0; i < entryCount; i++) {
57            ApplicationsState.AppEntry entry = apps.get(i);
58
59            // If it's a different user's app, skip it.
60            UsageStatsState extraData = (UsageStatsState) entry.extraInfo;
61            if (extraData.userId != currentUserId) {
62                continue;
63            }
64
65            final String packageName;
66            synchronized (entry) {
67                packageName = entry.info.packageName;
68            }
69            AppDeletionPreference preference =
70                    (AppDeletionPreference) cache.getCachedPreference(packageName);
71            if (preference == null) {
72                preference = new AppDeletionPreference(getContext(), entry);
73                preference.setKey(packageName);
74                preference.setOnPreferenceChangeListener(this);
75            }
76
77            addPreference(preference);
78            preference.setChecked(mBackend.isChecked(packageName));
79            preference.setOrder(i);
80            preference.updateSummary();
81        }
82        cache.removeCachedPrefs();
83        updateText();
84    }
85
86    @Override
87    public boolean onPreferenceChange(Preference preference, Object newValue) {
88        boolean isChecked = (boolean) newValue;
89
90        // If we have no AppDeletionType, we have no apps to toggle.
91        if (mBackend == null) {
92            return true;
93        }
94
95        if (preference == this) {
96            for (int i = 0; i < getPreferenceCount(); i++) {
97                AppDeletionPreference p = (AppDeletionPreference) getPreference(i);
98                p.setOnPreferenceChangeListener(null);
99                p.setChecked(isChecked);
100                mBackend.setChecked(p.getPackageName(), isChecked);
101                p.setOnPreferenceChangeListener(this);
102            }
103            updateText();
104            MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_ALL_APPS,
105                    isChecked);
106            return true;
107        }
108
109        // If a single preference changed, we need to toggle just itself.
110        AppDeletionPreference p = (AppDeletionPreference) preference;
111        mBackend.setChecked(p.getPackageName(), isChecked);
112        logAppToggle(isChecked, p.getPackageName());
113        updateText();
114        return true;
115    }
116
117    @Override
118    public void onClick() {
119        super.onClick();
120        MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_APPS_COLLAPSED,
121                isCollapsed());
122    }
123
124    /**
125     * Initializes the PreferenceGroup with a source of apps to list.
126     * @param type The AppDeletionType which provides the app list.
127     */
128    public void setDeletionType(AppDeletionType type) {
129        mBackend = type;
130    }
131
132    private void updateText() {
133        int eligibleApps = 0;
134        long freeableBytes = 0;
135        if (mBackend != null) {
136            eligibleApps = mBackend.getEligibleApps();
137            freeableBytes = mBackend.getTotalAppsFreeableSpace(true);
138        }
139        Context app = getContext();
140        setTitle(app.getString(R.string.deletion_helper_apps_group_title, eligibleApps));
141        setSummary(app.getString(R.string.deletion_helper_apps_group_summary,
142                Formatter.formatFileSize(app, freeableBytes),
143                AppStateUsageStatsBridge.UNUSED_DAYS_DELETION_THRESHOLD));
144    }
145
146    private void logAppToggle(boolean checked, String packageName) {
147        if (checked) {
148            MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_ON,
149                    packageName);
150        } else {
151            MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_SELECTION_APP_OFF,
152                    packageName);
153        }
154    }
155}
156