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