Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2014 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.tv.settings.device;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.DialogFragment;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.Intent;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.res.Resources;
     29 import android.graphics.drawable.Drawable;
     30 import android.os.Bundle;
     31 import android.os.Environment;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 import android.text.format.Formatter;
     35 
     36 import com.android.tv.settings.device.apps.AppsActivity;
     37 import com.android.tv.settings.device.storage.StorageItem;
     38 import com.android.tv.settings.device.storage.StorageMeasurement;
     39 import com.android.tv.settings.device.storage.StorageMeasurement.MeasurementDetails;
     40 import com.android.tv.settings.device.storage.StorageMeasurement.MeasurementReceiver;
     41 import com.android.tv.settings.device.storage.PercentageBarChart;
     42 import com.android.tv.settings.device.privacy.PrivacyActivity;
     43 
     44 import com.android.tv.settings.dialog.SettingsLayoutActivity;
     45 import com.android.tv.settings.dialog.Layout;
     46 import com.android.tv.settings.dialog.Layout.Header;
     47 import com.android.tv.settings.dialog.Layout.Action;
     48 import com.android.tv.settings.dialog.Layout.Status;
     49 import com.android.tv.settings.dialog.Layout.Static;
     50 import com.android.tv.settings.dialog.Layout.StringGetter;
     51 import com.android.tv.settings.dialog.Layout.LayoutGetter;
     52 import com.android.tv.settings.dialog.Layout.DrawableGetter;
     53 
     54 import com.android.tv.settings.R;
     55 
     56 import java.util.ArrayList;
     57 import java.util.Collections;
     58 import java.util.HashMap;
     59 import java.util.List;
     60 
     61 /**
     62  * Activity to view storage consumption and factory reset device.
     63  */
     64 public class StorageResetActivity extends SettingsLayoutActivity {
     65 
     66     private static final boolean DEBUG = false;
     67     private static final String TAG = "StorageResetActivity";
     68     private static final long INVALID_SIZE = -1;
     69     private static final int ACTION_RESET_DEVICE = 1;
     70     private static final int ACTION_CANCEL = 2;
     71     private static final int ACTION_CLEAR_CACHE = 3;
     72 
     73     /**
     74      * Support for shutdown-after-reset. If our launch intent has a true value for
     75      * the boolean extra under the following key, then include it in the intent we
     76      * use to trigger a factory reset. This will cause us to shut down instead of
     77      * restart after the reset.
     78      */
     79     private static final String SHUTDOWN_INTENT_EXTRA = "shutdown";
     80 
     81     private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
     82 
     83         private MeasurementDetails mLastMeasurementDetails = null;
     84 
     85         @Override
     86         public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) {
     87             if (mLastMeasurementDetails == null) {
     88                 StorageResetActivity.this.updateApproximate(totalSize, availSize);
     89             }
     90         }
     91 
     92         @Override
     93         public void updateDetails(StorageMeasurement meas, MeasurementDetails details) {
     94             mLastMeasurementDetails = details;
     95             StorageResetActivity.this.updateDetails(mLastMeasurementDetails);
     96         }
     97     };
     98 
     99     private class SizeStringGetter extends StringGetter {
    100         private long mSize = INVALID_SIZE;
    101 
    102         @Override
    103         public String get() {
    104             return String.format(getString(R.string.storage_size), formatSize(mSize));
    105         }
    106 
    107         public void setSize(long size) {
    108             mSize = size;
    109             refreshView();
    110         }
    111     };
    112 
    113     private class StorageDrawableGetter extends DrawableGetter {
    114         Drawable mDrawable = null;
    115 
    116         @Override
    117         public Drawable get() {
    118             if (mDrawable == null) {
    119                 return mRes.getDrawable(R.drawable.ic_settings_storage);
    120             } else {
    121                 return mDrawable;
    122             }
    123         }
    124 
    125         void setDrawable(ArrayList<PercentageBarChart.Entry> entries) {
    126             mDrawable = new PercentageBarChart(entries, mRes.getColor(R.color.storage_avail),
    127                 getPixelSize(R.dimen.storage_bar_min_tick_width),
    128                 getPixelSize(R.dimen.content_fragment_icon_width),
    129                 getPixelSize(R.dimen.content_fragment_icon_width), isLayoutRtl());
    130             refreshView();
    131         }
    132     };
    133 
    134     private Resources mRes;
    135     private StorageMeasurement mMeasure;
    136     private boolean mResumed = false;
    137     private final SizeStringGetter mAppsSize = new SizeStringGetter();
    138     private final SizeStringGetter mDcimSize = new SizeStringGetter();
    139     private final SizeStringGetter mMusicSize = new SizeStringGetter();
    140     private final SizeStringGetter mDownloadsSize = new SizeStringGetter();
    141     private final SizeStringGetter mCacheSize = new SizeStringGetter();
    142     private final SizeStringGetter mMiscSize = new SizeStringGetter();
    143     private final SizeStringGetter mAvailSize = new SizeStringGetter();
    144     private final SizeStringGetter mStorageDescription = new SizeStringGetter();
    145     private final StorageDrawableGetter mStorageDrawable = new StorageDrawableGetter();
    146 
    147     @Override
    148     protected void onCreate(Bundle savedInstanceState) {
    149         mRes = getResources();
    150         super.onCreate(savedInstanceState);
    151         mMeasure = StorageMeasurement.getInstance(this, null);
    152     }
    153 
    154     @Override
    155     protected void onResume() {
    156         super.onResume();
    157         mResumed = true;
    158         mMeasure.setReceiver(mReceiver);
    159         mMeasure.invalidate();
    160         mMeasure.measure();
    161     }
    162 
    163     @Override
    164     protected void onPause() {
    165         mMeasure.cleanUp();
    166         mResumed = false;
    167         super.onPause();
    168     }
    169 
    170     @Override
    171     public Layout createLayout() {
    172         final Intent appsIntent = new Intent(this, AppsActivity.class);
    173         return
    174             new Layout().breadcrumb(getString(R.string.header_category_device))
    175                 .add(new Header.Builder(mRes)
    176                         .icon(mStorageDrawable)
    177                         .title(R.string.device_storage_reset)
    178                         .build()
    179                     .add(new Header.Builder(mRes)
    180                             .title(R.string.storage_title)
    181                             .description(mStorageDescription)
    182                             .build()
    183                         .add(new Action.Builder(mRes, appsIntent)
    184                             .title(R.string.storage_apps_usage)
    185                             .icon(R.drawable.storage_indicator_apps)
    186                             .description(mAppsSize)
    187                             .build())
    188                         .add(new Status.Builder(mRes)
    189                             .title(R.string.storage_dcim_usage)
    190                             .icon(R.drawable.storage_indicator_dcim)
    191                             .description(mDcimSize)
    192                             .build())
    193                         .add(new Status.Builder(mRes)
    194                             .title(R.string.storage_music_usage)
    195                             .icon(R.drawable.storage_indicator_music)
    196                             .description(mMusicSize)
    197                             .build())
    198                         .add(new Status.Builder(mRes)
    199                             .title(R.string.storage_downloads_usage)
    200                             .icon(R.drawable.storage_indicator_downloads)
    201                             .description(mDownloadsSize)
    202                             .build())
    203                         .add(new Action.Builder(mRes, ACTION_CLEAR_CACHE)
    204                             .title(R.string.storage_media_cache_usage)
    205                             .icon(R.drawable.storage_indicator_cache)
    206                             .description(mCacheSize)
    207                             .build())
    208                         .add(new Status.Builder(mRes)
    209                             .title(R.string.storage_media_misc_usage)
    210                             .icon(R.drawable.storage_indicator_misc)
    211                             .description(mMiscSize)
    212                             .build())
    213                         .add(new Status.Builder(mRes)
    214                             .title(R.string.storage_available)
    215                             .icon(R.drawable.storage_indicator_available)
    216                             .description(mAvailSize)
    217                             .build())
    218                     )
    219                     .add(new Header.Builder(mRes)
    220                             .title(R.string.device_reset)
    221                             .build()
    222                         .add(new Header.Builder(mRes)
    223                                 .title(R.string.device_reset)
    224                                 .build()
    225                             .add(new Action.Builder(mRes, ACTION_RESET_DEVICE)
    226                                     .title(R.string.confirm_factory_reset_device)
    227                                     .build()
    228                             )
    229                             .add(new Action.Builder(mRes, Action.ACTION_BACK)
    230                                 .title(R.string.title_cancel)
    231                                 .defaultSelection()
    232                                 .build())
    233                         )
    234                         .add(new Action.Builder(mRes, Action.ACTION_BACK)
    235                             .title(R.string.title_cancel)
    236                             .defaultSelection()
    237                             .build())
    238                     )
    239                 );
    240     }
    241 
    242     @Override
    243     public void onActionClicked(Action action) {
    244         switch (action.getId()) {
    245             case ACTION_RESET_DEVICE:
    246                 if (!ActivityManager.isUserAMonkey()) {
    247                     Intent resetIntent = new Intent("android.intent.action.MASTER_CLEAR");
    248                     if (getIntent().getBooleanExtra(SHUTDOWN_INTENT_EXTRA, false)) {
    249                         resetIntent.putExtra(SHUTDOWN_INTENT_EXTRA, true);
    250                     }
    251                     sendBroadcast(resetIntent);
    252                 }
    253                 break;
    254             case ACTION_CANCEL:
    255                 goBackToTitle(getString(R.string.device_storage_reset));
    256                 break;
    257             case ACTION_CLEAR_CACHE:
    258                 final DialogFragment fragment = ConfirmClearCacheFragment.newInstance();
    259                 fragment.show(getFragmentManager(), null);
    260                 break;
    261             default:
    262                 final Intent intent = action.getIntent();
    263                 if (intent != null) {
    264                     startActivity(intent);
    265                 }
    266         }
    267     }
    268 
    269     private void updateStorageSize(long availSize, long appsSize, long dcimSize, long musicSize,
    270             long downloadsSize, long cacheSize, long miscSize) {
    271         mAvailSize.setSize(availSize);
    272         mAppsSize.setSize(appsSize);
    273         mDcimSize.setSize(dcimSize);
    274         mMusicSize.setSize(musicSize);
    275         mDownloadsSize.setSize(downloadsSize);
    276         mCacheSize.setSize(cacheSize);
    277         mMiscSize.setSize(miscSize);
    278     }
    279 
    280     private int getPixelSize(int resource) {
    281         return mRes.getDimensionPixelSize(resource);
    282     }
    283 
    284     void updateStorageDescription(long totalSize, ArrayList<PercentageBarChart.Entry> entries) {
    285         mStorageDescription.setSize(totalSize);
    286         mStorageDrawable.setDrawable(entries);
    287     }
    288 
    289     private void updateApproximate(long totalSize, long availSize) {
    290 
    291         final long usedSize = totalSize - availSize;
    292         ArrayList<PercentageBarChart.Entry> entries = new ArrayList<PercentageBarChart.Entry>();
    293         entries.add(new PercentageBarChart.Entry(
    294                 0, usedSize / (float) totalSize, android.graphics.Color.GRAY));
    295 
    296         updateStorageSize(availSize, INVALID_SIZE, INVALID_SIZE, INVALID_SIZE, INVALID_SIZE,
    297                 INVALID_SIZE, INVALID_SIZE);
    298         updateStorageDescription(totalSize, entries);
    299     }
    300 
    301     private void addEntry(List<PercentageBarChart.Entry> entries, StorageItem storageItem,
    302             long size, long totalSize) {
    303         if (size > 0) {
    304             entries.add(new PercentageBarChart.Entry(
    305                     storageItem.ordinal(), size / (float) totalSize,
    306                     storageItem.getColor(getResources())));
    307         }
    308     }
    309 
    310     private void updateDetails(MeasurementDetails details) {
    311 
    312         final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
    313                 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
    314 
    315         final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
    316                 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
    317                 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
    318 
    319         final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
    320 
    321         ArrayList<PercentageBarChart.Entry> entries = new ArrayList<PercentageBarChart.Entry>();
    322 
    323         addEntry(entries, StorageItem.APPS, details.appsSize, details.totalSize);
    324         addEntry(entries, StorageItem.PICTURES_VIDEO, dcimSize, details.totalSize);
    325         addEntry(entries, StorageItem.AUDIO, musicSize, details.totalSize);
    326         addEntry(entries, StorageItem.DOWNLOADS, downloadsSize, details.totalSize);
    327         addEntry(entries, StorageItem.CACHED_DATA, details.cacheSize, details.totalSize);
    328         addEntry(entries, StorageItem.MISC, details.miscSize, details.totalSize);
    329 
    330         Collections.sort(entries);
    331 
    332         updateStorageSize(details.availSize, details.appsSize, dcimSize, musicSize, downloadsSize,
    333                 details.cacheSize, details.miscSize);
    334         updateStorageDescription(details.totalSize, entries);
    335     }
    336 
    337     private static long totalValues(HashMap<String, Long> map, String... keys) {
    338         long total = 0;
    339         for (String key : keys) {
    340             if (map.containsKey(key)) {
    341                 total += map.get(key);
    342             }
    343         }
    344         return total;
    345     }
    346 
    347     private String formatSize(long size) {
    348         return (size == INVALID_SIZE) ? getString(R.string.storage_calculating_size)
    349                 : Formatter.formatShortFileSize(this, size);
    350     }
    351 
    352     /**
    353      * Dialog to request user confirmation before clearing all cache data.
    354      */
    355     public static class ConfirmClearCacheFragment extends DialogFragment {
    356         public static ConfirmClearCacheFragment newInstance() {
    357             return new ConfirmClearCacheFragment();
    358         }
    359 
    360         @Override
    361         public Dialog onCreateDialog(Bundle savedInstanceState) {
    362             final Context context = getActivity();
    363 
    364             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    365             builder.setTitle(R.string.device_storage_clear_cache_title);
    366             builder.setMessage(getString(R.string.device_storage_clear_cache_message));
    367 
    368             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    369                 @Override
    370                 public void onClick(DialogInterface dialog, int which) {
    371                     final PackageManager pm = context.getPackageManager();
    372                     final List<PackageInfo> infos = pm.getInstalledPackages(0);
    373                     for (PackageInfo info : infos) {
    374                         pm.deleteApplicationCacheFiles(info.packageName, null);
    375                     }
    376                 }
    377             });
    378             builder.setNegativeButton(android.R.string.cancel, null);
    379 
    380             return builder.create();
    381         }
    382     }
    383 
    384 }
    385