Home | History | Annotate | Download | only in deletionhelper
      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.storagemanager.deletionhelper;
     18 
     19 import android.app.Activity;
     20 import android.app.Application;
     21 import android.os.Bundle;
     22 import android.util.ArraySet;
     23 import android.util.Log;
     24 import com.android.internal.logging.MetricsLogger;
     25 import com.android.internal.logging.MetricsProto.MetricsEvent;
     26 import com.android.settingslib.applications.ApplicationsState;
     27 
     28 import java.util.ArrayList;
     29 import java.util.HashSet;
     30 import java.util.List;
     31 
     32 /**
     33  * AppDeletionType provides a list of apps which have not been used for a while on the system.
     34  * It also provides the functionality to clear out these apps.
     35  */
     36 public class AppDeletionType implements DeletionType, ApplicationsState.Callbacks,
     37         AppStateBaseBridge.Callback {
     38     public static final String EXTRA_CHECKED_SET = "checkedSet";
     39     private static final String TAG = "AppDeletionType";
     40 
     41     private FreeableChangedListener mListener;
     42     private AppListener mAppListener;
     43     private List<ApplicationsState.AppEntry> mAppEntries;
     44     private ApplicationsState mState;
     45     private ApplicationsState.Session mSession;
     46     private HashSet<String> mCheckedApplications;
     47     private AppStateUsageStatsBridge mDataUsageBridge;
     48 
     49     public AppDeletionType(Application app, HashSet<String> checkedApplications) {
     50         mState = ApplicationsState.getInstance(app);
     51         mSession = mState.newSession(this);
     52         if (checkedApplications != null) {
     53             mCheckedApplications = checkedApplications;
     54         } else {
     55             mCheckedApplications = new HashSet<>();
     56         }
     57         mDataUsageBridge = new AppStateUsageStatsBridge(app.getApplicationContext(), mState, this);
     58     }
     59 
     60     @Override
     61     public void registerFreeableChangedListener(FreeableChangedListener listener) {
     62         mListener = listener;
     63     }
     64 
     65     @Override
     66     public void onResume() {
     67         mSession.resume();
     68         mDataUsageBridge.resume();
     69     }
     70 
     71     @Override
     72     public void onPause() {
     73         mSession.pause();
     74         mDataUsageBridge.pause();
     75     }
     76 
     77     @Override
     78     public void onSaveInstanceStateBundle(Bundle savedInstanceState) {
     79         savedInstanceState.putSerializable(EXTRA_CHECKED_SET, mCheckedApplications);
     80     }
     81 
     82     @Override
     83     public void clearFreeableData(Activity activity) {
     84         ArraySet<String> apps = new ArraySet<>();
     85         for (ApplicationsState.AppEntry entry : mAppEntries) {
     86             final String packageName;
     87             synchronized (entry) {
     88                 packageName = entry.info.packageName;
     89             }
     90             if (mCheckedApplications.contains(packageName)) {
     91                 apps.add(packageName);
     92             }
     93         }
     94         // TODO: If needed, add an action on the callback.
     95         PackageDeletionTask task = new PackageDeletionTask(activity.getPackageManager(), apps,
     96                 new PackageDeletionTask.Callback() {
     97                     @Override
     98                     public void onSuccess() {
     99                     }
    100 
    101                     @Override
    102                     public void onError() {
    103                         Log.e(TAG, "An error occurred while uninstalling packages.");
    104                         MetricsLogger.action(activity,
    105                                 MetricsEvent.ACTION_DELETION_HELPER_APPS_DELETION_FAIL);
    106                     }
    107                 });
    108 
    109         task.run();
    110     }
    111 
    112     @Override
    113     public void onRunningStateChanged(boolean running) {
    114 
    115     }
    116 
    117     @Override
    118     public void onPackageListChanged() {
    119     }
    120 
    121     @Override
    122     public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
    123         if (apps == null) {
    124             return;
    125         }
    126         mAppEntries = apps;
    127         for (ApplicationsState.AppEntry app : mAppEntries) {
    128             synchronized (app) {
    129                 mState.ensureIcon(app);
    130             }
    131         }
    132         if (mAppListener != null) {
    133             mAppListener.onAppRebuild(mAppEntries);
    134         }
    135         maybeNotifyListener();
    136     }
    137 
    138     @Override
    139     public void onPackageIconChanged() {
    140     }
    141 
    142     @Override
    143     public void onPackageSizeChanged(String packageName) {
    144         rebuild();
    145     }
    146 
    147     @Override
    148     public void onAllSizesComputed() {
    149         rebuild();
    150     }
    151 
    152     @Override
    153     public void onLauncherInfoChanged() {
    154 
    155     }
    156 
    157     @Override
    158     public void onLoadEntriesCompleted() {
    159         rebuild();
    160     }
    161 
    162     @Override
    163     public void onExtraInfoUpdated() {
    164         rebuild();
    165     }
    166 
    167     /**
    168      * Registers a preference group view to notify when the app list changes.
    169      */
    170     public void registerView(AppDeletionPreferenceGroup preference) {
    171         mAppListener = preference;
    172     }
    173 
    174     /**
    175      * Set a package to be checked for deletion, if the apps are cleared.
    176      * @param packageName The name of the package to potentially delete.
    177      * @param isChecked Whether or not the package should be deleted.
    178      */
    179     public void setChecked(String packageName, boolean isChecked) {
    180         if (isChecked) {
    181             mCheckedApplications.add(packageName);
    182         } else {
    183             mCheckedApplications.remove(packageName);
    184         }
    185         maybeNotifyListener();
    186     }
    187 
    188     /**
    189      * Returns an amount of clearable app data.
    190      * @param countUnchecked If unchecked applications should be counted for size purposes.
    191      */
    192     public long getTotalAppsFreeableSpace(boolean countUnchecked) {
    193         long freeableSpace = 0;
    194         if (mAppEntries != null) {
    195             for (int i = 0; i < mAppEntries.size(); i++) {
    196                 final ApplicationsState.AppEntry entry = mAppEntries.get(i);
    197                 long entrySize = entry.size;
    198                 final String packageName;
    199                 synchronized (entry) {
    200                     packageName = entry.info.packageName;
    201                 }
    202                 // If the entrySize is negative, it is either an unknown size or an error occurred.
    203                 if ((countUnchecked ||
    204                         mCheckedApplications.contains(packageName)) && entrySize > 0) {
    205                     freeableSpace += entrySize;
    206                 }
    207             }
    208         }
    209 
    210         return freeableSpace;
    211     }
    212 
    213     /**
    214      * Returns a number of eligible, clearable apps.
    215      */
    216     public int getEligibleApps() {
    217         if (mAppEntries == null) {
    218             return 0;
    219         }
    220         return mAppEntries.size();
    221     }
    222 
    223     /**
    224      * Returns if a given package is slated for deletion.
    225      * @param packageName The name of the package to check.
    226      */
    227     public boolean isChecked(String packageName) {
    228         return mCheckedApplications.contains(packageName);
    229     }
    230 
    231     private void rebuild() {
    232         mSession.rebuild(AppStateUsageStatsBridge.FILTER_USAGE_STATS,
    233                 ApplicationsState.SIZE_COMPARATOR);
    234     }
    235 
    236     private void maybeNotifyListener() {
    237         if (mListener != null) {
    238             mListener.onFreeableChanged(mAppEntries.size(), getTotalAppsFreeableSpace(true));
    239         }
    240     }
    241 
    242     /**
    243      * An interface for listening for when the app list has been rebuilt.
    244      */
    245     public interface AppListener {
    246         /**
    247          * Callback to be called once the app list is rebuilt.
    248          * @param apps A list of eligible, clearable AppEntries.
    249          */
    250         void onAppRebuild(List<ApplicationsState.AppEntry> apps);
    251     }
    252 }
    253