blob: 0c50c356c79e57d14aad99cd810c6e9697fc49bc [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.storagemanager.deletionhelper;
import android.app.Activity;
import android.app.LoaderManager;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.storagemanager.deletionhelper.AppsAsyncLoader.AppFilter;
import com.android.storagemanager.deletionhelper.AppsAsyncLoader.PackageInfo;
import java.util.HashSet;
import java.util.List;
/**
* AppDeletionType provides a list of apps which have not been used for a while on the system. It
* also provides the functionality to clear out these apps.
*/
public class AppDeletionType
implements LoaderManager.LoaderCallbacks<List<PackageInfo>>, DeletionType {
public static final String EXTRA_CHECKED_SET = "checkedSet";
private static final String TAG = "AppDeletionType";
private static final int LOADER_ID = 25;
public static final String THRESHOLD_TYPE_KEY = "threshold_type";
public static final int BUNDLE_CAPACITY = 1;
private FreeableChangedListener mListener;
private AppListener mAppListener;
private HashSet<String> mCheckedApplications;
private Context mContext;
private int mThresholdType;
private List<PackageInfo> mApps;
private int mLoadingStatus;
public AppDeletionType(
DeletionHelperSettings fragment,
HashSet<String> checkedApplications,
int thresholdType) {
mLoadingStatus = LoadingStatus.LOADING;
mThresholdType = thresholdType;
mContext = fragment.getContext();
if (checkedApplications != null) {
mCheckedApplications = checkedApplications;
} else {
mCheckedApplications = new HashSet<>();
}
Bundle bundle = new Bundle(BUNDLE_CAPACITY);
bundle.putInt(THRESHOLD_TYPE_KEY, mThresholdType);
// NOTE: This is not responsive to package changes. Bug filed for seeing if feature is
// necessary b/35065979
fragment.getLoaderManager().initLoader(LOADER_ID, bundle, this);
}
@Override
public void registerFreeableChangedListener(FreeableChangedListener listener) {
mListener = listener;
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onSaveInstanceStateBundle(Bundle savedInstanceState) {
savedInstanceState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
}
@Override
public void clearFreeableData(Activity activity) {
if (mApps == null) {
return;
}
ArraySet<String> apps = new ArraySet<>();
for (PackageInfo app : mApps) {
final String packageName = app.packageName;
if (mCheckedApplications.contains(packageName)) {
apps.add(packageName);
}
}
// TODO: If needed, add an action on the callback.
PackageDeletionTask task = new PackageDeletionTask(activity.getPackageManager(), apps,
new PackageDeletionTask.Callback() {
@Override
public void onSuccess() {
}
@Override
public void onError() {
Log.e(TAG, "An error occurred while uninstalling packages.");
MetricsLogger.action(activity,
MetricsEvent.ACTION_DELETION_HELPER_APPS_DELETION_FAIL);
}
});
task.run();
}
/**
* Registers a preference group view to notify when the app list changes.
*/
public void registerView(AppDeletionPreferenceGroup preference) {
mAppListener = preference;
}
/**
* Set a package to be checked for deletion, if the apps are cleared.
* @param packageName The name of the package to potentially delete.
* @param isChecked Whether or not the package should be deleted.
*/
public void setChecked(String packageName, boolean isChecked) {
if (isChecked) {
mCheckedApplications.add(packageName);
} else {
mCheckedApplications.remove(packageName);
}
maybeNotifyListener();
}
/**
* Returns an amount of clearable app data.
* @param countUnchecked If unchecked applications should be counted for size purposes.
*/
public long getTotalAppsFreeableSpace(boolean countUnchecked) {
long freeableSpace = 0;
if (mApps != null) {
for (int i = 0, size = mApps.size(); i < size; i++) {
final PackageInfo app = mApps.get(i);
long appSize = app.size;
final String packageName = app.packageName;
// If the appSize is negative, it is either an unknown size or an error occurred.
if ((countUnchecked || mCheckedApplications.contains(packageName)) && appSize > 0) {
freeableSpace += appSize;
}
}
}
return freeableSpace;
}
/**
* Returns if a given package is slated for deletion.
* @param packageName The name of the package to check.
*/
public boolean isChecked(String packageName) {
return mCheckedApplications.contains(packageName);
}
private AppFilter getFilter(int mThresholdType) {
switch (mThresholdType) {
case AppsAsyncLoader.NO_THRESHOLD:
return AppsAsyncLoader.FILTER_NO_THRESHOLD;
case AppsAsyncLoader.NORMAL_THRESHOLD:
default:
return AppsAsyncLoader.FILTER_USAGE_STATS;
}
}
private void maybeNotifyListener() {
if (mListener != null) {
mListener.onFreeableChanged(
mApps.size(),
getTotalAppsFreeableSpace(DeletionHelperSettings.COUNT_CHECKED_ONLY));
}
}
public long getDeletionThreshold() {
switch (mThresholdType) {
case AppsAsyncLoader.NO_THRESHOLD:
// The threshold is actually Long.MIN_VALUE but we don't want to display that to
// the user.
return 0;
case AppsAsyncLoader.NORMAL_THRESHOLD:
default:
return AppsAsyncLoader.UNUSED_DAYS_DELETION_THRESHOLD;
}
}
@Override
public int getLoadingStatus() {
return mLoadingStatus;
}
@Override
public int getContentCount() {
return mApps.size();
}
@Override
public void setLoadingStatus(@LoadingStatus int loadingStatus) {
mLoadingStatus = loadingStatus;
}
@Override
public Loader<List<PackageInfo>> onCreateLoader(int id, Bundle args) {
return new AppsAsyncLoader.Builder(mContext)
.setUid(UserHandle.myUserId())
.setUuid(VolumeInfo.ID_PRIVATE_INTERNAL)
.setStorageStatsSource(new StorageStatsSource(mContext))
.setPackageManager(mContext.getPackageManager())
.setUsageStatsManager(
(UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE))
.setFilter(
getFilter(
args.getInt(THRESHOLD_TYPE_KEY, AppsAsyncLoader.NORMAL_THRESHOLD)))
.build();
}
@Override
public void onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data) {
mApps = data;
updateLoadingStatus();
maybeNotifyListener();
mAppListener.onAppRebuild(mApps);
}
@Override
public void onLoaderReset(Loader<List<PackageInfo>> loader) {}
/**
* An interface for listening for when the app list has been rebuilt.
*/
public interface AppListener {
/**
* Callback to be called once the app list is rebuilt.
*
* @param apps A list of eligible, clearable AppEntries.
*/
void onAppRebuild(List<PackageInfo> apps);
}
}