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.Manifest;
     20 import android.app.Activity;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.pm.PackageManager;
     24 import android.os.Bundle;
     25 import android.os.storage.StorageManager;
     26 import android.support.annotation.VisibleForTesting;
     27 import android.support.v14.preference.PreferenceFragment;
     28 import android.support.v7.preference.Preference;
     29 import android.support.v7.preference.PreferenceScreen;
     30 import android.text.format.Formatter;
     31 import android.view.LayoutInflater;
     32 import android.view.Menu;
     33 import android.view.MenuInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.widget.Button;
     37 import com.android.internal.logging.MetricsLogger;
     38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     39 import com.android.internal.util.Preconditions;
     40 import com.android.settingslib.HelpUtils;
     41 import com.android.settingslib.applications.AppUtils;
     42 import com.android.storagemanager.ButtonBarProvider;
     43 import com.android.storagemanager.R;
     44 import com.android.storagemanager.overlay.DeletionHelperFeatureProvider;
     45 import com.android.storagemanager.overlay.FeatureFactory;
     46 import java.util.ArrayList;
     47 import java.util.HashSet;
     48 import java.util.List;
     49 
     50 /**
     51  * Settings screen for the deletion helper, which manually removes data which is not recently used.
     52  */
     53 public class DeletionHelperSettings extends PreferenceFragment
     54         implements DeletionType.FreeableChangedListener, View.OnClickListener {
     55     public static final boolean COUNT_UNCHECKED = true;
     56     public static final boolean COUNT_CHECKED_ONLY = false;
     57 
     58     protected static final String APPS_KEY = "apps_group";
     59     protected static final String KEY_DOWNLOADS_PREFERENCE = "delete_downloads";
     60     protected static final String KEY_PHOTOS_VIDEOS_PREFERENCE = "delete_photos";
     61     protected static final String KEY_GAUGE_PREFERENCE = "deletion_gauge";
     62 
     63     private static final String THRESHOLD_KEY = "threshold_key";
     64     private static final int DOWNLOADS_LOADER_ID = 1;
     65     private static final int NUM_DELETION_TYPES = 3;
     66     private static final long UNSET = -1;
     67 
     68     private List<DeletionType> mDeletableContentList;
     69     private AppDeletionPreferenceGroup mApps;
     70     @VisibleForTesting AppDeletionType mAppBackend;
     71     private DownloadsDeletionPreferenceGroup mDownloadsPreference;
     72     private DownloadsDeletionType mDownloadsDeletion;
     73     private PhotosDeletionPreference mPhotoPreference;
     74     private Preference mGaugePreference;
     75     private DeletionType mPhotoVideoDeletion;
     76     private Button mCancel, mFree;
     77     private DeletionHelperFeatureProvider mProvider;
     78     private int mThresholdType;
     79     private LoadingSpinnerController mLoadingController;
     80 
     81     public static DeletionHelperSettings newInstance(int thresholdType) {
     82         DeletionHelperSettings instance = new DeletionHelperSettings();
     83         Bundle bundle = new Bundle(1);
     84         bundle.putInt(THRESHOLD_KEY, thresholdType);
     85         instance.setArguments(bundle);
     86         return instance;
     87     }
     88 
     89     @Override
     90     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
     91         addPreferencesFromResource(R.xml.deletion_helper_list);
     92         mThresholdType = getArguments().getInt(THRESHOLD_KEY, AppsAsyncLoader.NORMAL_THRESHOLD);
     93         mApps = (AppDeletionPreferenceGroup) findPreference(APPS_KEY);
     94         mPhotoPreference = (PhotosDeletionPreference) findPreference(KEY_PHOTOS_VIDEOS_PREFERENCE);
     95         mProvider = FeatureFactory.getFactory(getActivity()).getDeletionHelperFeatureProvider();
     96         mLoadingController = new LoadingSpinnerController((DeletionHelperActivity) getActivity());
     97         if (mProvider != null) {
     98             mPhotoVideoDeletion =
     99                     mProvider.createPhotoVideoDeletionType(getContext(), mThresholdType);
    100         }
    101 
    102         HashSet<String> checkedApplications = null;
    103         if (savedInstanceState != null) {
    104             checkedApplications =
    105                     (HashSet<String>) savedInstanceState.getSerializable(
    106                             AppDeletionType.EXTRA_CHECKED_SET);
    107         }
    108         mAppBackend = new AppDeletionType(this, checkedApplications, mThresholdType);
    109         mAppBackend.registerView(mApps);
    110         mAppBackend.registerFreeableChangedListener(this);
    111         mApps.setDeletionType(mAppBackend);
    112 
    113         mDeletableContentList = new ArrayList<>(NUM_DELETION_TYPES);
    114 
    115         mGaugePreference = findPreference(KEY_GAUGE_PREFERENCE);
    116         Activity activity = getActivity();
    117         if (activity != null && mGaugePreference != null) {
    118             Intent intent = activity.getIntent();
    119             if (intent != null) {
    120                 CharSequence gaugeTitle =
    121                         getGaugeString(getContext(), intent, activity.getCallingPackage());
    122                 if (gaugeTitle != null) {
    123                     mGaugePreference.setTitle(gaugeTitle);
    124                 } else {
    125                     getPreferenceScreen().removePreference(mGaugePreference);
    126                 }
    127             }
    128         }
    129     }
    130 
    131     protected static CharSequence getGaugeString(
    132             Context context, Intent intent, String packageName) {
    133         Preconditions.checkNotNull(intent);
    134         long requestedBytes = intent.getLongExtra(StorageManager.EXTRA_REQUESTED_BYTES, UNSET);
    135         if (requestedBytes > 0) {
    136             CharSequence callerLabel =
    137                     AppUtils.getApplicationLabel(context.getPackageManager(), packageName);
    138             // I really hope this isn't the case, but I can't ignore the possibility that we cannot
    139             // determine what app the referrer is.
    140             if (callerLabel == null) {
    141                 return null;
    142             }
    143             return context.getString(
    144                     R.string.app_requesting_space,
    145                     callerLabel,
    146                     Formatter.formatFileSize(context, requestedBytes));
    147         }
    148         return null;
    149     }
    150 
    151     @Override
    152     public void onActivityCreated(Bundle savedInstanceState) {
    153         super.onActivityCreated(savedInstanceState);
    154         initializeButtons();
    155         setHasOptionsMenu(true);
    156         Activity activity = getActivity();
    157         if (activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
    158                 != PackageManager.PERMISSION_GRANTED) {
    159             activity.requestPermissions(
    160                     new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
    161                     0);
    162         }
    163 
    164         if (mProvider != null && mPhotoVideoDeletion != null) {
    165             mPhotoPreference.setDaysToKeep(mProvider.getDaysToKeep(mThresholdType));
    166             mPhotoPreference.registerFreeableChangedListener(this);
    167             mPhotoPreference.registerDeletionService(mPhotoVideoDeletion);
    168             mDeletableContentList.add(mPhotoVideoDeletion);
    169         } else {
    170             getPreferenceScreen().removePreference(mPhotoPreference);
    171             mPhotoPreference.setEnabled(false);
    172         }
    173 
    174         String[] uncheckedFiles = null;
    175         if (savedInstanceState != null) {
    176             uncheckedFiles =
    177                     savedInstanceState.getStringArray(
    178                             DownloadsDeletionType.EXTRA_UNCHECKED_DOWNLOADS);
    179         }
    180         mDownloadsPreference =
    181                 (DownloadsDeletionPreferenceGroup) findPreference(KEY_DOWNLOADS_PREFERENCE);
    182         mDownloadsDeletion = new DownloadsDeletionType(getActivity(), uncheckedFiles);
    183         mDownloadsPreference.registerFreeableChangedListener(this);
    184         mDownloadsPreference.registerDeletionService(mDownloadsDeletion);
    185         mDeletableContentList.add(mDownloadsDeletion);
    186         if (isEmptyState()) {
    187             setupEmptyState();
    188         }
    189         mDeletableContentList.add(mAppBackend);
    190         updateFreeButtonText();
    191     }
    192 
    193     @VisibleForTesting
    194     void setupEmptyState() {
    195         final PreferenceScreen screen = getPreferenceScreen();
    196         if (mDownloadsPreference != null) {
    197             mDownloadsPreference.setChecked(false);
    198             screen.removePreference(mDownloadsPreference);
    199         }
    200         screen.removePreference(mApps);
    201 
    202         // Nulling out the downloads preferences means we won't accidentally delete what isn't
    203         // visible.
    204         mDownloadsDeletion = null;
    205         mDownloadsPreference = null;
    206     }
    207 
    208     private boolean isEmptyState() {
    209         // We know we are in the empty state if our loader is not using a threshold.
    210         return mThresholdType == AppsAsyncLoader.NO_THRESHOLD;
    211     }
    212 
    213     @Override
    214     public void onResume() {
    215         super.onResume();
    216 
    217         mLoadingController.initializeLoading(getListView());
    218 
    219         for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
    220             mDeletableContentList.get(i).onResume();
    221         }
    222 
    223         if (mDownloadsDeletion != null
    224                 && getActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
    225                         == PackageManager.PERMISSION_GRANTED) {
    226             getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(), mDownloadsDeletion);
    227         }
    228     }
    229 
    230     @Override
    231     public void onPause() {
    232         super.onPause();
    233         for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
    234             mDeletableContentList.get(i).onPause();
    235         }
    236     }
    237 
    238     @Override
    239     public void onSaveInstanceState(Bundle outState) {
    240         super.onSaveInstanceState(outState);
    241         for (int i = 0, size = mDeletableContentList.size(); i < size; i++) {
    242             mDeletableContentList.get(i).onSaveInstanceStateBundle(outState);
    243         }
    244     }
    245 
    246     @Override
    247     public void onFreeableChanged(int numItems, long bytesFreeable) {
    248         if (numItems > 0 || bytesFreeable > 0 || allTypesEmpty()) {
    249             if (mLoadingController != null) {
    250                 mLoadingController.onCategoryLoad();
    251             }
    252         }
    253 
    254         // bytesFreeable is the number of bytes freed by a single deletion type. If it is non-zero,
    255         // there is stuff to free and we can enable it. If it is zero, though, we still need to get
    256         // getTotalFreeableSpace to check all deletion types.
    257         if (mFree != null) {
    258             mFree.setEnabled(bytesFreeable != 0 || getTotalFreeableSpace(COUNT_CHECKED_ONLY) != 0);
    259         }
    260         updateFreeButtonText();
    261 
    262         // Transition to empty state if all types have reported there is nothing to delete. Skip
    263         // the transition if we are already in no threshold mode
    264         if (allTypesEmpty() && !isEmptyState()) {
    265             startEmptyState();
    266         }
    267     }
    268 
    269     private boolean allTypesEmpty() {
    270         return mAppBackend.isEmpty()
    271                 && (mDownloadsDeletion == null || mDownloadsDeletion.isEmpty())
    272                 && (mPhotoVideoDeletion == null || mPhotoVideoDeletion.isEmpty());
    273     }
    274 
    275     private void startEmptyState() {
    276         if (getActivity() instanceof DeletionHelperActivity) {
    277             DeletionHelperActivity activity = (DeletionHelperActivity) getActivity();
    278             activity.setIsEmptyState(true /* isEmptyState */);
    279         }
    280     }
    281 
    282     /** Clears out the selected apps and data from the device and closes the fragment. */
    283     protected void clearData() {
    284         // This should be fine as long as there is only one extra deletion feature.
    285         // In the future, this should be done in an async queue in order to not
    286         // interfere with the simultaneous PackageDeletionTask.
    287         if (mPhotoPreference != null && mPhotoPreference.isChecked()) {
    288             mPhotoVideoDeletion.clearFreeableData(getActivity());
    289         }
    290         if (mDownloadsPreference != null) {
    291             mDownloadsDeletion.clearFreeableData(getActivity());
    292         }
    293         if (mAppBackend != null) {
    294             mAppBackend.clearFreeableData(getActivity());
    295         }
    296     }
    297 
    298     @Override
    299     public void onClick(View v) {
    300         if (v.getId() == R.id.next_button) {
    301             ConfirmDeletionDialog dialog =
    302                     ConfirmDeletionDialog.newInstance(getTotalFreeableSpace(COUNT_CHECKED_ONLY));
    303             // The 0 is a placeholder for an optional result code.
    304             dialog.setTargetFragment(this, 0);
    305             dialog.show(getFragmentManager(), ConfirmDeletionDialog.TAG);
    306             MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_HELPER_CLEAR);
    307         } else {
    308             MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETION_HELPER_CANCEL);
    309             getActivity().finish();
    310         }
    311     }
    312 
    313     @Override
    314     public void onRequestPermissionsResult(int requestCode, String permissions[],
    315                                            int[] grantResults) {
    316         if (requestCode == 0) {
    317             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    318                 mDownloadsDeletion.onResume();
    319                 getLoaderManager().initLoader(DOWNLOADS_LOADER_ID, new Bundle(),
    320                         mDownloadsDeletion);
    321             }
    322         }
    323     }
    324 
    325     @Override
    326     public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
    327         Activity activity = getActivity();
    328         String mHelpUri = getResources().getString(R.string.help_uri_deletion_helper);
    329         if (mHelpUri != null && activity != null) {
    330             HelpUtils.prepareHelpMenuItem(activity, menu, mHelpUri, getClass().getName());
    331         }
    332     }
    333 
    334     @Override
    335     public View onCreateView(
    336             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    337         View view = super.onCreateView(inflater, container, savedInstanceState);
    338         return view;
    339     }
    340 
    341     @VisibleForTesting
    342     void setDownloadsDeletionType(DownloadsDeletionType downloadsDeletion) {
    343         mDownloadsDeletion = downloadsDeletion;
    344     }
    345 
    346     private void initializeButtons() {
    347         ButtonBarProvider activity = (ButtonBarProvider) getActivity();
    348         activity.getButtonBar().setVisibility(View.VISIBLE);
    349 
    350         mCancel = activity.getSkipButton();
    351         mCancel.setText(R.string.cancel);
    352         mCancel.setOnClickListener(this);
    353         mCancel.setVisibility(View.VISIBLE);
    354 
    355         mFree = activity.getNextButton();
    356         mFree.setText(R.string.storage_menu_free);
    357         mFree.setOnClickListener(this);
    358         mFree.setEnabled(false);
    359     }
    360 
    361     private void updateFreeButtonText() {
    362         Activity activity = getActivity();
    363         if (activity == null) {
    364             return;
    365         }
    366         mFree.setText(
    367                 String.format(
    368                         activity.getString(R.string.deletion_helper_free_button),
    369                         Formatter.formatFileSize(
    370                                 activity, getTotalFreeableSpace(COUNT_CHECKED_ONLY))));
    371     }
    372 
    373     private long getTotalFreeableSpace(boolean countUnchecked) {
    374         long freeableSpace = 0;
    375         freeableSpace += mAppBackend.getTotalAppsFreeableSpace(countUnchecked);
    376         if (mPhotoPreference != null) {
    377             freeableSpace += mPhotoPreference.getFreeableBytes(countUnchecked);
    378         }
    379         if (mDownloadsPreference != null) {
    380             freeableSpace += mDownloadsDeletion.getFreeableBytes(countUnchecked);
    381         }
    382         return freeableSpace;
    383     }
    384 
    385 }
    386