Add a deletion helper view for clearing space.

This view provides an interface which allows users to clear out
apps and other data which they may no longer need to alleviate
storage pressure. This implementation gives the bits to uninstall
apps which have not been used within the last 60 days from the
system.

A future patch will add in the ability to extend the deletion helper
view to have additional clearing types.

Change-Id: I65cba54ca247d52227e67e2fa9b84fe63fb8ab34
(cherry picked from commit a671272bf6)
This commit is contained in:
Daniel Nishi
2016-04-11 14:08:44 -07:00
parent 80f32e1dce
commit b84304d4e6
12 changed files with 686 additions and 1 deletions

View File

@@ -0,0 +1,262 @@
package com.android.settings.deletionhelper;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.text.format.Formatter;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.R;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.deletionhelper.AppStateUsageStatsBridge;
import com.android.settings.deletionhelper.AppDeletionPreference;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.ApplicationsState.Session;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
/**
* Settings screen for the deletion helper, which manually removes data which is not recently used.
*/
public class DeletionHelperFragment extends SettingsPreferenceFragment implements
ApplicationsState.Callbacks, AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener {
private static final String TAG = "DeletionHelperFragment";
private static final String EXTRA_HAS_BRIDGE = "hasBridge";
private static final String EXTRA_HAS_SIZES = "hasSizes";
private static final String EXTRA_CHECKED_SET = "checkedSet";
private static final String KEY_APPS_GROUP = "apps_group";
private Button mCancel, mFree;
private PreferenceGroup mApps;
private ApplicationsState mState;
private Session mSession;
private HashSet<String> mUncheckedApplications;
private AppStateUsageStatsBridge mDataUsageBridge;
private ArrayList<AppEntry> mAppEntries;
private boolean mHasReceivedAppEntries, mHasReceivedBridgeCallback, mFinishedLoading;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mState = ApplicationsState.getInstance(getActivity().getApplication());
mSession = mState.newSession(this);
mUncheckedApplications = new HashSet<>();
mDataUsageBridge = new AppStateUsageStatsBridge(getActivity(), mState, this);
addPreferencesFromResource(R.xml.deletion_helper_list);
mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP);
if (savedInstanceState != null) {
mHasReceivedAppEntries =
savedInstanceState.getBoolean(EXTRA_HAS_SIZES, false);
mHasReceivedBridgeCallback =
savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
mUncheckedApplications =
(HashSet<String>) savedInstanceState.getSerializable(EXTRA_CHECKED_SET);
}
}
private void initializeButtons(View v) {
mCancel = (Button) v.findViewById(R.id.back_button);
mCancel.setText(R.string.cancel);
mCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finishFragment();
}
});
mFree = (Button) v.findViewById(R.id.next_button);
mFree.setText(R.string.storage_menu_free);
mFree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ArraySet<String> apps = new ArraySet<>();
for (AppEntry entry : mAppEntries) {
if (!mUncheckedApplications.contains(entry.label)) {
synchronized (entry) {
apps.add(entry.info.packageName);
}
}
}
// TODO: If needed, add an action on the callback.
PackageDeletionTask task = new PackageDeletionTask(
getActivity().getPackageManager(), apps,
new PackageDeletionTask.Callback() {
@Override
public void onSuccess() {
}
@Override
public void onError() {
Log.e(TAG, "An error occurred while uninstalling packages.");
}
});
finishFragment();
task.run();
}
});
}
@Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
initializeButtons(v);
setLoading(true, false);
}
@Override
public void onResume() {
super.onResume();
mSession.resume();
mDataUsageBridge.resume();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_HAS_SIZES, mHasReceivedAppEntries);
outState.putBoolean(EXTRA_HAS_BRIDGE, mHasReceivedBridgeCallback);
outState.putSerializable(EXTRA_CHECKED_SET, mUncheckedApplications);
}
@Override
public void onPause() {
super.onPause();
mDataUsageBridge.pause();
mSession.pause();
}
private void rebuild() {
// Only rebuild if we have the packages and their usage stats.
if (!mHasReceivedBridgeCallback || !mHasReceivedAppEntries) {
return;
}
final ArrayList<AppEntry> apps =
mSession.rebuild(AppStateUsageStatsBridge.FILTER_USAGE_STATS,
ApplicationsState.SIZE_COMPARATOR);
mAppEntries = apps;
cacheRemoveAllPrefs(mApps);
int entryCount = apps.size();
for (int i = 0; i < entryCount; i++) {
AppEntry entry = apps.get(i);
final String packageName = entry.label;
AppDeletionPreference preference = (AppDeletionPreference) getCachedPreference(entry.label);
if (preference == null) {
preference = new AppDeletionPreference(getActivity(), entry,
mState);
preference.setKey(packageName);
preference.setChecked(!mUncheckedApplications.contains(packageName));
preference.setOnPreferenceChangeListener(this);
mApps.addPreference(preference);
}
preference.setOrder(i);
}
removeCachedPrefs(mApps);
// All applications should be filled in if we've received the sizes.
// setLoading being called multiple times causes flickering, so we only do it once.
if (mHasReceivedAppEntries && !mFinishedLoading) {
mFinishedLoading = true;
setLoading(false, true);
getButtonBar().setVisibility(View.VISIBLE);
}
updateFreeButtonText();
}
private void updateFreeButtonText() {
mFree.setText(String.format(getActivity().getString(R.string.deletion_helper_free_button),
Formatter.formatFileSize(getActivity(), getTotalFreeableSpace())));
}
@Override
public void onRunningStateChanged(boolean running) {
// No-op.
}
@Override
public void onPackageListChanged() {
rebuild();
}
@Override
public void onRebuildComplete(ArrayList<AppEntry> apps) {
}
@Override
public void onPackageIconChanged() {
}
@Override
public void onPackageSizeChanged(String packageName) {
rebuild();
}
@Override
public void onAllSizesComputed() {
rebuild();
}
@Override
public void onLauncherInfoChanged() {
}
@Override
public void onLoadEntriesCompleted() {
mHasReceivedAppEntries = true;
rebuild();
}
@Override
public void onExtraInfoUpdated() {
mHasReceivedBridgeCallback = true;
rebuild();
}
@Override
protected int getMetricsCategory() {
return MetricsEvent.DEVICEINFO_STORAGE;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean checked = (boolean) newValue;
String packageName = ((AppDeletionPreference) preference).getPackageName();
if (checked) {
mUncheckedApplications.remove(packageName);
} else {
mUncheckedApplications.add(packageName);
}
updateFreeButtonText();
return true;
}
private long getTotalFreeableSpace() {
long freeableSpace = 0;
for (int i = 0; i < mAppEntries.size(); i++) {
final AppEntry entry = mAppEntries.get(i);
if (!mUncheckedApplications.contains(entry.label)) {
freeableSpace += mAppEntries.get(i).size;
}
}
return freeableSpace;
}
}