OSDN Git Service

Update the summary strings in the Deletion Helper to match the latest mocks.
[android-x86/packages-apps-Settings.git] / src / com / android / settings / deletionhelper / DeletionHelperFragment.java
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.settings.deletionhelper;
18
19 import android.app.Activity;
20 import android.app.Application;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.support.v7.preference.Preference;
24 import android.text.format.Formatter;
25 import android.util.ArraySet;
26 import android.util.Log;
27 import android.view.View;
28 import android.widget.Button;
29 import com.android.settings.CollapsibleCheckboxPreferenceGroup;
30 import com.android.settings.PhotosDeletionPreference;
31 import com.android.settings.SettingsPreferenceFragment;
32 import com.android.settings.R;
33 import com.android.internal.logging.MetricsProto.MetricsEvent;
34 import com.android.settings.applications.AppStateBaseBridge;
35 import com.android.settings.overlay.DeletionHelperFeatureProvider;
36 import com.android.settings.overlay.FeatureFactory;
37 import com.android.settingslib.applications.ApplicationsState;
38 import com.android.settingslib.applications.ApplicationsState.AppEntry;
39 import com.android.settingslib.applications.ApplicationsState.Callbacks;
40 import com.android.settingslib.applications.ApplicationsState.Session;
41
42 import java.util.ArrayList;
43 import java.util.HashSet;
44
45 /**
46  * Settings screen for the deletion helper, which manually removes data which is not recently used.
47  */
48 public class DeletionHelperFragment extends SettingsPreferenceFragment implements
49         ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
50         Preference.OnPreferenceChangeListener, DeletionType.FreeableChangedListener,
51         View.OnClickListener {
52     public static final int CLEAR_DATA_RESULT = 1;
53     public static final String FREED_BYTES_KEY = "freed";
54
55     private static final String TAG = "DeletionHelperFragment";
56
57     private static final String EXTRA_HAS_BRIDGE = "hasBridge";
58     private static final String EXTRA_HAS_SIZES = "hasSizes";
59     private static final String EXTRA_CHECKED_SET = "checkedSet";
60
61     private static final String KEY_APPS_GROUP = "apps_group";
62     private static final String KEY_PHOTOS_VIDEOS_PREFERENCE = "delete_photos";
63     private static final String KEY_DOWNLOADS_PREFERENCE = "delete_downloads";
64
65     private static final int DOWNLOADS_LOADER_ID = 1;
66
67     private Button mCancel, mFree;
68     private CollapsibleCheckboxPreferenceGroup mApps;
69     private PhotosDeletionPreference mPhotoPreference;
70     private DownloadsDeletionPreferenceGroup mDownloadsPreference;
71
72     private ApplicationsState mState;
73     private Session mSession;
74     private HashSet<String> mCheckedApplications;
75     private AppStateUsageStatsBridge mDataUsageBridge;
76     private ArrayList<AppEntry> mAppEntries;
77     private boolean mHasReceivedAppEntries, mHasReceivedBridgeCallback, mFinishedLoading;
78     private DeletionHelperFeatureProvider mProvider;
79     private DeletionType mPhotoVideoDeletion;
80     private DownloadsDeletionType mDownloadsDeletion;
81
82     @Override
83     public void onCreate(Bundle savedInstanceState) {
84         super.onCreate(savedInstanceState);
85         setAnimationAllowed(true);
86         Application app = getActivity().getApplication();
87         mState = ApplicationsState.getInstance(app);
88         mSession = mState.newSession(this);
89         mCheckedApplications = new HashSet<>();
90         mDataUsageBridge = new AppStateUsageStatsBridge(getActivity(), mState, this);
91
92         addPreferencesFromResource(R.xml.deletion_helper_list);
93         mApps = (CollapsibleCheckboxPreferenceGroup) findPreference(KEY_APPS_GROUP);
94         mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE);
95         mDownloadsPreference =
96                 (DownloadsDeletionPreferenceGroup) findPreference(KEY_DOWNLOADS_PREFERENCE);
97         mProvider =
98                 FeatureFactory.getFactory(app).getDeletionHelperFeatureProvider();
99         if (mProvider != null) {
100             mPhotoVideoDeletion = mProvider.createPhotoVideoDeletionType(getContext());
101         }
102         mDownloadsDeletion = new DownloadsDeletionType(getActivity());
103
104         if (savedInstanceState != null) {
105             mHasReceivedAppEntries =
106                     savedInstanceState.getBoolean(EXTRA_HAS_SIZES, false);
107             mHasReceivedBridgeCallback =
108                     savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
109             mCheckedApplications =
110                     (HashSet<String>) savedInstanceState.getSerializable(EXTRA_CHECKED_SET);
111         }
112     }
113
114     private void initializeButtons(View v) {
115         mCancel = (Button) v.findViewById(R.id.skip_button);
116         mCancel.setText(R.string.cancel);
117         mCancel.setOnClickListener(this);
118         mCancel.setVisibility(View.VISIBLE);
119
120         mFree = (Button) v.findViewById(R.id.next_button);
121         mFree.setText(R.string.storage_menu_free);
122         mFree.setOnClickListener(this);
123
124         Button back = (Button) v.findViewById(R.id.back_button);
125         back.setVisibility(View.GONE);
126     }
127
128     private void initializeDeletionPreferences() {
129         if (mProvider == null) {
130             getPreferenceScreen().removePreference(mPhotoPreference);
131             mPhotoPreference = null;
132         } else {
133             mPhotoPreference.registerFreeableChangedListener(this);
134             mPhotoPreference.registerDeletionService(mPhotoVideoDeletion);
135         }
136
137         mDownloadsPreference.registerFreeableChangedListener(this);
138         mDownloadsPreference.registerDeletionService(mDownloadsDeletion);
139         mApps.setOnPreferenceChangeListener(this);
140     }
141
142     @Override
143     public void onViewCreated(View v, Bundle savedInstanceState) {
144         super.onViewCreated(v, savedInstanceState);
145         initializeButtons(v);
146         initializeDeletionPreferences();
147         setLoading(true, false);
148     }
149
150     @Override
151     public void onResume() {
152         super.onResume();
153         mSession.resume();
154         mDataUsageBridge.resume();
155         mDownloadsDeletion.onResume();
156         getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);
157
158         if (mPhotoVideoDeletion != null) {
159             mPhotoVideoDeletion.onResume();
160         }
161     }
162
163
164     @Override
165     public void onSaveInstanceState(Bundle outState) {
166         super.onSaveInstanceState(outState);
167         outState.putBoolean(EXTRA_HAS_SIZES, mHasReceivedAppEntries);
168         outState.putBoolean(EXTRA_HAS_BRIDGE, mHasReceivedBridgeCallback);
169         outState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
170     }
171
172
173     @Override
174     public void onPause() {
175         super.onPause();
176         mDataUsageBridge.pause();
177         mSession.pause();
178         mDownloadsDeletion.onPause();
179
180         if (mPhotoVideoDeletion != null) {
181             mPhotoVideoDeletion.onPause();
182         }
183     }
184
185     private void rebuild() {
186         // Only rebuild if we have the packages and their usage stats.
187         if (!mHasReceivedBridgeCallback || !mHasReceivedAppEntries) {
188             return;
189         }
190
191         final ArrayList<AppEntry> apps =
192                 mSession.rebuild(AppStateUsageStatsBridge.FILTER_USAGE_STATS,
193                         ApplicationsState.SIZE_COMPARATOR);
194         if (apps == null) return;
195         mAppEntries = apps;
196         refreshAppGroup(apps);
197
198         // All applications should be filled in if we've received the sizes.
199         // setLoading being called multiple times causes flickering, so we only do it once.
200         if (mHasReceivedAppEntries && !mFinishedLoading) {
201             mFinishedLoading = true;
202             setLoading(false, true);
203             getButtonBar().setVisibility(View.VISIBLE);
204         }
205         updateFreeButtonText();
206     }
207
208     private void updateFreeButtonText() {
209         mFree.setText(String.format(getActivity().getString(R.string.deletion_helper_free_button),
210                 Formatter.formatFileSize(getActivity(), getTotalFreeableSpace())));
211     }
212
213     @Override
214     public void onRunningStateChanged(boolean running) {
215         // No-op.
216     }
217
218     @Override
219     public void onPackageListChanged() {
220         rebuild();
221     }
222
223     @Override
224     public void onRebuildComplete(ArrayList<AppEntry> apps) {
225     }
226
227     @Override
228     public void onPackageIconChanged() {
229     }
230
231     @Override
232     public void onPackageSizeChanged(String packageName) {
233         rebuild();
234     }
235
236     @Override
237     public void onAllSizesComputed() {
238         rebuild();
239     }
240
241     @Override
242     public void onLauncherInfoChanged() {
243     }
244
245     @Override
246     public void onLoadEntriesCompleted() {
247         mHasReceivedAppEntries = true;
248         rebuild();
249     }
250
251     @Override
252     public void onExtraInfoUpdated() {
253         mHasReceivedBridgeCallback = true;
254         rebuild();
255     }
256
257     @Override
258     protected int getMetricsCategory() {
259         return MetricsEvent.DEVICEINFO_STORAGE;
260     }
261
262     @Override
263     public boolean onPreferenceChange(Preference preference, Object newValue) {
264         boolean checked = (boolean) newValue;
265         if (preference.getKey().equals(mApps.getKey())) {
266             return toggleAllApps(checked);
267         }
268
269         String packageName = ((AppDeletionPreference) preference).getPackageName();
270         if (checked) {
271             mCheckedApplications.add(packageName);
272         } else {
273             mCheckedApplications.remove(packageName);
274
275             // We remove the preference change listener to avoid toggling every app on and off.
276             mApps.setOnPreferenceChangeListener(null);
277             mApps.setChecked(false);
278             mApps.setOnPreferenceChangeListener(this);
279         }
280         updateFreeButtonText();
281         return true;
282     }
283
284     @Override
285     public void onFreeableChanged(int numItems, long freeableBytes) {
286         updateFreeButtonText();
287     }
288
289     @Override
290     public void onClick(View v) {
291         if (v.getId() == mFree.getId()) {
292             ConfirmDeletionDialog dialog =
293                     ConfirmDeletionDialog.newInstance(getTotalFreeableSpace());
294             // The 0 is a placeholder for an optional result code.
295             dialog.setTargetFragment(this, 0);
296             dialog.show(getFragmentManager(), ConfirmDeletionDialog.TAG);
297         } else {
298             finishFragment();
299         }
300     }
301
302     /**
303      * Clears out the selected apps and data from the device and closes the fragment.
304      */
305     protected void clearData() {
306         // This should be fine as long as there is only one extra deletion feature.
307         // In the future, this should be done in an async queue in order to not
308         // interfere with the simultaneous PackageDeletionTask.
309         if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
310             mPhotoVideoDeletion.clearFreeableData();
311         }
312         mDownloadsDeletion.clearFreeableData();
313
314         ArraySet<String> apps = new ArraySet<>();
315         for (AppEntry entry : mAppEntries) {
316             if (mCheckedApplications.contains(entry.label)) {
317                 synchronized (entry) {
318                     apps.add(entry.info.packageName);
319                 }
320             }
321         }
322         // TODO: If needed, add an action on the callback.
323         PackageDeletionTask task = new PackageDeletionTask(getActivity().getPackageManager(), apps,
324                 new PackageDeletionTask.Callback() {
325                     @Override
326                     public void onSuccess() {
327                     }
328
329                     @Override
330                     public void onError() {
331                         Log.e(TAG, "An error occurred while uninstalling packages.");
332                     }
333                 });
334         Intent data = new Intent();
335         data.putExtra(FREED_BYTES_KEY, getTotalFreeableSpace());
336         getActivity().setResult(CLEAR_DATA_RESULT, data);
337
338         task.run();
339         finishFragment();
340     }
341
342     private long getTotalFreeableSpace() {
343         long freeableSpace = 0;
344         freeableSpace += getTotalAppsFreeableSpace(false);
345         if (mPhotoPreference != null) {
346             freeableSpace += mPhotoPreference.getFreeableBytes();
347         }
348         freeableSpace += mDownloadsDeletion.getFreeableBytes();
349         return freeableSpace;
350     }
351
352     private void refreshAppGroup(ArrayList<AppEntry> apps) {
353         int entryCount = apps.size();
354         cacheRemoveAllPrefs(mApps);
355         for (int i = 0; i < entryCount; i++) {
356             AppEntry entry = apps.get(i);
357             final String packageName = entry.label;
358             AppDeletionPreference preference =
359                     (AppDeletionPreference) getCachedPreference(entry.label);
360             if (preference == null) {
361                 preference = new AppDeletionPreference(getActivity(), entry, mState);
362                 preference.setKey(packageName);
363                 preference.setOnPreferenceChangeListener(this);
364                 mApps.addPreference(preference);
365             }
366             preference.setChecked(mCheckedApplications.contains(packageName));
367             preference.setOrder(i);
368         }
369         removeCachedPrefs(mApps);
370         updateAppsGroupText();
371     }
372
373     private long getTotalAppsFreeableSpace(boolean countUnchecked) {
374         long freeableSpace = 0;
375         if (mAppEntries != null) {
376             for (int i = 0; i < mAppEntries.size(); i++) {
377                 final AppEntry entry = mAppEntries.get(i);
378                 long entrySize = mAppEntries.get(i).size;
379                 // If the entrySize is negative, it is either an unknown size or an error occurred.
380                 if ((countUnchecked ||
381                         mCheckedApplications.contains(entry.label)) && entrySize > 0) {
382                     freeableSpace += entrySize;
383                 }
384             }
385         }
386
387         return freeableSpace;
388     }
389
390     private void updateAppsGroupText() {
391         if (mAppEntries != null) {
392             Activity app = getActivity();
393             mApps.setTitle(app.getString(R.string.deletion_helper_apps_group_title,
394                     mAppEntries.size()));
395             mApps.setSummary(app.getString(R.string.deletion_helper_apps_group_summary,
396                     Formatter.formatFileSize(app,
397                             getTotalAppsFreeableSpace(true)),
398                     AppStateUsageStatsBridge.UNUSED_DAYS_DELETION_THRESHOLD));
399         }
400     }
401
402     private boolean toggleAllApps(boolean checked) {
403         for (AppEntry entry : mAppEntries) {
404             final String packageName = entry.label;
405             if (checked) {
406                 mCheckedApplications.add(packageName);
407             } else {
408                 mCheckedApplications.remove(packageName);
409             }
410         }
411         refreshAppGroup(mAppEntries);
412         updateFreeButtonText();
413         return true;
414     }
415 }