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