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