• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.settings.deviceinfo;
18 
19 import android.app.ActivityThread;
20 import android.app.DownloadManager;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.IPackageManager;
24 import android.content.res.Resources;
25 import android.graphics.drawable.ShapeDrawable;
26 import android.graphics.drawable.shapes.RectShape;
27 import android.os.Bundle;
28 import android.os.Environment;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.os.storage.StorageManager;
33 import android.os.storage.StorageVolume;
34 import android.preference.Preference;
35 import android.preference.PreferenceCategory;
36 import android.text.format.Formatter;
37 
38 import com.android.settings.R;
39 import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver;
40 
41 import java.util.HashSet;
42 import java.util.Set;
43 
44 public class StorageVolumePreferenceCategory extends PreferenceCategory implements
45         MeasurementReceiver {
46 
47     static final int TOTAL_SIZE = 0;
48     static final int APPLICATIONS = 1;
49     static final int DCIM = 2; // Pictures and Videos
50     static final int MUSIC = 3;
51     static final int DOWNLOADS = 4;
52     static final int MISC = 5;
53     static final int AVAILABLE = 6;
54 
55     private UsageBarPreference mUsageBarPreference;
56     private Preference[] mPreferences;
57     private Preference mMountTogglePreference;
58     private Preference mFormatPreference;
59     private Preference mStorageLow;
60     private int[] mColors;
61 
62     private Resources mResources;
63 
64     private StorageVolume mStorageVolume;
65 
66     private StorageManager mStorageManager = null;
67 
68     private StorageMeasurement mMeasurement;
69 
70     private boolean mAllowFormat;
71 
72     static class CategoryInfo {
73         final int mTitle;
74         final int mColor;
75 
CategoryInfo(int title, int color)76         public CategoryInfo(int title, int color) {
77             mTitle = title;
78             mColor = color;
79         }
80     }
81 
82     static final CategoryInfo[] sCategoryInfos = new CategoryInfo[] {
83         new CategoryInfo(R.string.memory_size, 0),
84         new CategoryInfo(R.string.memory_apps_usage, R.color.memory_apps_usage),
85         new CategoryInfo(R.string.memory_dcim_usage, R.color.memory_dcim),
86         new CategoryInfo(R.string.memory_music_usage, R.color.memory_music),
87         new CategoryInfo(R.string.memory_downloads_usage, R.color.memory_downloads),
88         new CategoryInfo(R.string.memory_media_misc_usage, R.color.memory_misc),
89         new CategoryInfo(R.string.memory_available, R.color.memory_avail),
90     };
91 
92     public static final Set<String> sPathsExcludedForMisc = new HashSet<String>();
93 
94     static class MediaCategory {
95         final String[] mDirPaths;
96         final int mCategory;
97         //final int mMediaType;
98 
MediaCategory(int category, String... directories)99         public MediaCategory(int category, String... directories) {
100             mCategory = category;
101             final int length = directories.length;
102             mDirPaths = new String[length];
103             for (int i = 0; i < length; i++) {
104                 final String name = directories[i];
105                 final String path = Environment.getExternalStoragePublicDirectory(name).
106                         getAbsolutePath();
107                 mDirPaths[i] = path;
108                 sPathsExcludedForMisc.add(path);
109             }
110         }
111     }
112 
113     static final MediaCategory[] sMediaCategories = new MediaCategory[] {
114         new MediaCategory(DCIM, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
115                 Environment.DIRECTORY_PICTURES),
116         new MediaCategory(MUSIC, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS,
117                 Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_RINGTONES,
118                 Environment.DIRECTORY_PODCASTS)
119     };
120 
121     static {
122         // Downloads
123         sPathsExcludedForMisc.add(Environment.getExternalStoragePublicDirectory(
124                 Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
125         // Apps
126         sPathsExcludedForMisc.add(Environment.getExternalStorageDirectory().getAbsolutePath() +
127                 "/Android");
128     }
129 
130     // Updates the memory usage bar graph.
131     private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
132 
133     // Updates the memory usage bar graph.
134     private static final int MSG_UI_UPDATE_EXACT = 2;
135 
136     private Handler mUpdateHandler = new Handler() {
137         @Override
138         public void handleMessage(Message msg) {
139             switch (msg.what) {
140                 case MSG_UI_UPDATE_APPROXIMATE: {
141                     Bundle bundle = msg.getData();
142                     final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE);
143                     final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE);
144                     updateApproximate(totalSize, availSize);
145                     break;
146                 }
147                 case MSG_UI_UPDATE_EXACT: {
148                     Bundle bundle = msg.getData();
149                     final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE);
150                     final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE);
151                     final long appsUsed = bundle.getLong(StorageMeasurement.APPS_USED);
152                     final long downloadsSize = bundle.getLong(StorageMeasurement.DOWNLOADS_SIZE);
153                     final long miscSize = bundle.getLong(StorageMeasurement.MISC_SIZE);
154                     final long[] mediaSizes = bundle.getLongArray(StorageMeasurement.MEDIA_SIZES);
155                     updateExact(totalSize, availSize, appsUsed, downloadsSize, miscSize,
156                             mediaSizes);
157                     break;
158                 }
159             }
160         }
161     };
162 
StorageVolumePreferenceCategory(Context context, Resources resources, StorageVolume storageVolume, StorageManager storageManager, boolean isPrimary)163     public StorageVolumePreferenceCategory(Context context, Resources resources,
164             StorageVolume storageVolume, StorageManager storageManager, boolean isPrimary) {
165         super(context);
166         mResources = resources;
167         mStorageVolume = storageVolume;
168         mStorageManager = storageManager;
169         setTitle(storageVolume != null ? storageVolume.getDescription(context)
170                 : resources.getText(R.string.internal_storage));
171         mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary);
172         mMeasurement.setReceiver(this);
173 
174         // Cannot format emulated storage
175         mAllowFormat = mStorageVolume != null && !mStorageVolume.isEmulated();
176         // For now we are disabling reformatting secondary external storage
177         // until some interoperability problems with MTP are fixed
178         if (!isPrimary) mAllowFormat = false;
179     }
180 
init()181     public void init() {
182         mUsageBarPreference = new UsageBarPreference(getContext());
183 
184         final int width = (int) mResources.getDimension(R.dimen.device_memory_usage_button_width);
185         final int height = (int) mResources.getDimension(R.dimen.device_memory_usage_button_height);
186 
187         final int numberOfCategories = sCategoryInfos.length;
188         mPreferences = new Preference[numberOfCategories];
189         mColors = new int[numberOfCategories];
190         for (int i = 0; i < numberOfCategories; i++) {
191             final Preference preference = new Preference(getContext());
192             mPreferences[i] = preference;
193             preference.setTitle(sCategoryInfos[i].mTitle);
194             preference.setSummary(R.string.memory_calculating_size);
195             if (i != TOTAL_SIZE) {
196                 // TOTAL_SIZE has no associated color
197                 mColors[i] = mResources.getColor(sCategoryInfos[i].mColor);
198                 preference.setIcon(createRectShape(width, height, mColors[i]));
199             }
200         }
201 
202         mMountTogglePreference = new Preference(getContext());
203         mMountTogglePreference.setTitle(R.string.sd_eject);
204         mMountTogglePreference.setSummary(R.string.sd_eject_summary);
205 
206         if (mAllowFormat) {
207             mFormatPreference = new Preference(getContext());
208             mFormatPreference.setTitle(R.string.sd_format);
209             mFormatPreference.setSummary(R.string.sd_format_summary);
210         }
211 
212         final IPackageManager pm = ActivityThread.getPackageManager();
213         try {
214             if (pm.isStorageLow()) {
215                 mStorageLow = new Preference(getContext());
216                 mStorageLow.setTitle(R.string.storage_low_title);
217                 mStorageLow.setSummary(R.string.storage_low_summary);
218             } else {
219                 mStorageLow = null;
220             }
221         } catch (RemoteException e) {
222         }
223     }
224 
getStorageVolume()225     public StorageVolume getStorageVolume() {
226         return mStorageVolume;
227     }
228 
229     /**
230      * Successive mounts can change the list of visible preferences.
231      * This makes sure all preferences are visible and displayed in the right order.
232      */
resetPreferences()233     private void resetPreferences() {
234         final int numberOfCategories = sCategoryInfos.length;
235 
236         removePreference(mUsageBarPreference);
237         for (int i = 0; i < numberOfCategories; i++) {
238             removePreference(mPreferences[i]);
239         }
240         removePreference(mMountTogglePreference);
241         if (mFormatPreference != null) {
242             removePreference(mFormatPreference);
243         }
244 
245         addPreference(mUsageBarPreference);
246         if (mStorageLow != null) {
247             addPreference(mStorageLow);
248         }
249         for (int i = 0; i < numberOfCategories; i++) {
250             addPreference(mPreferences[i]);
251         }
252         addPreference(mMountTogglePreference);
253         if (mFormatPreference != null) {
254             addPreference(mFormatPreference);
255         }
256 
257         mMountTogglePreference.setEnabled(true);
258     }
259 
updatePreferencesFromState()260     private void updatePreferencesFromState() {
261         resetPreferences();
262 
263         String state = mStorageVolume != null
264                 ? mStorageManager.getVolumeState(mStorageVolume.getPath())
265                 : Environment.MEDIA_MOUNTED;
266 
267         String readOnly = "";
268         if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
269             state = Environment.MEDIA_MOUNTED;
270             readOnly = mResources.getString(R.string.read_only);
271             if (mFormatPreference != null) {
272                 removePreference(mFormatPreference);
273             }
274         }
275 
276         if ((mStorageVolume == null || !mStorageVolume.isRemovable())
277                 && !Environment.MEDIA_UNMOUNTED.equals(state)) {
278             // This device has built-in storage that is not removable.
279             // There is no reason for the user to unmount it.
280             removePreference(mMountTogglePreference);
281         }
282 
283         if (Environment.MEDIA_MOUNTED.equals(state)) {
284             mPreferences[AVAILABLE].setSummary(mPreferences[AVAILABLE].getSummary() + readOnly);
285 
286             mMountTogglePreference.setEnabled(true);
287             mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
288             mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary));
289         } else {
290             if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
291                     || Environment.MEDIA_UNMOUNTABLE.equals(state)) {
292                 mMountTogglePreference.setEnabled(true);
293                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
294                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary));
295             } else {
296                 mMountTogglePreference.setEnabled(false);
297                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
298                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary));
299             }
300 
301             removePreference(mUsageBarPreference);
302             removePreference(mPreferences[TOTAL_SIZE]);
303             removePreference(mPreferences[AVAILABLE]);
304             if (mFormatPreference != null) {
305                 removePreference(mFormatPreference);
306             }
307         }
308     }
309 
updateApproximate(long totalSize, long availSize)310     public void updateApproximate(long totalSize, long availSize) {
311         mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize));
312         mPreferences[AVAILABLE].setSummary(formatSize(availSize));
313 
314         final long usedSize = totalSize - availSize;
315 
316         mUsageBarPreference.clear();
317         mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY);
318         mUsageBarPreference.commit();
319 
320         updatePreferencesFromState();
321     }
322 
updateExact(long totalSize, long availSize, long appsSize, long downloadsSize, long miscSize, long[] mediaSizes)323     public void updateExact(long totalSize, long availSize, long appsSize, long downloadsSize,
324             long miscSize, long[] mediaSizes) {
325         mUsageBarPreference.clear();
326 
327         mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize));
328 
329         if (mMeasurement.isExternalSDCard()) {
330             // TODO FIXME: external SD card will not report any size. Show used space in bar graph
331             final long usedSize = totalSize - availSize;
332             mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY);
333         }
334 
335         updatePreference(appsSize, totalSize, APPLICATIONS);
336 
337         long totalMediaSize = 0;
338         for (int i = 0; i < sMediaCategories.length; i++) {
339             final int category = sMediaCategories[i].mCategory;
340             final long size = mediaSizes[i];
341             updatePreference(size, totalSize, category);
342             totalMediaSize += size;
343         }
344 
345         updatePreference(downloadsSize, totalSize, DOWNLOADS);
346 
347         // Note miscSize != totalSize - availSize - appsSize - downloadsSize - totalMediaSize
348         // Block size is taken into account. That can be extra space from folders. TODO Investigate
349         updatePreference(miscSize, totalSize, MISC);
350 
351         updatePreference(availSize, totalSize, AVAILABLE);
352 
353         mUsageBarPreference.commit();
354     }
355 
updatePreference(long size, long totalSize, int category)356     private void updatePreference(long size, long totalSize, int category) {
357         if (size > 0) {
358             mPreferences[category].setSummary(formatSize(size));
359             mUsageBarPreference.addEntry(size / (float) totalSize, mColors[category]);
360         } else {
361             removePreference(mPreferences[category]);
362         }
363     }
364 
measure()365     private void measure() {
366         mMeasurement.invalidate();
367         mMeasurement.measure();
368     }
369 
onResume()370     public void onResume() {
371         mMeasurement.setReceiver(this);
372         measure();
373     }
374 
onStorageStateChanged()375     public void onStorageStateChanged() {
376         measure();
377     }
378 
onMediaScannerFinished()379     public void onMediaScannerFinished() {
380         measure();
381     }
382 
onPause()383     public void onPause() {
384         mMeasurement.cleanUp();
385     }
386 
createRectShape(int width, int height, int color)387     private static ShapeDrawable createRectShape(int width, int height, int color) {
388         ShapeDrawable shape = new ShapeDrawable(new RectShape());
389         shape.setIntrinsicHeight(height);
390         shape.setIntrinsicWidth(width);
391         shape.getPaint().setColor(color);
392         return shape;
393     }
394 
formatSize(long size)395     private String formatSize(long size) {
396         return Formatter.formatFileSize(getContext(), size);
397     }
398 
399     @Override
updateApproximate(Bundle bundle)400     public void updateApproximate(Bundle bundle) {
401         final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE);
402         message.setData(bundle);
403         mUpdateHandler.sendMessage(message);
404     }
405 
406     @Override
updateExact(Bundle bundle)407     public void updateExact(Bundle bundle) {
408         final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXACT);
409         message.setData(bundle);
410         mUpdateHandler.sendMessage(message);
411     }
412 
mountToggleClicked(Preference preference)413     public boolean mountToggleClicked(Preference preference) {
414         return preference == mMountTogglePreference;
415     }
416 
intentForClick(Preference preference)417     public Intent intentForClick(Preference preference) {
418         Intent intent = null;
419 
420         // TODO The current "delete" story is not fully handled by the respective applications.
421         // When it is done, make sure the intent types below are correct.
422         // If that cannot be done, remove these intents.
423         if (preference == mFormatPreference) {
424             intent = new Intent(Intent.ACTION_VIEW);
425             intent.setClass(getContext(), com.android.settings.MediaFormat.class);
426             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume);
427         } else if (preference == mPreferences[APPLICATIONS]) {
428             intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
429             intent.setClass(getContext(),
430                     com.android.settings.Settings.ManageApplicationsActivity.class);
431         } else if (preference == mPreferences[DOWNLOADS]) {
432             intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
433                     DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
434         } else if (preference == mPreferences[MUSIC]) {
435             intent = new Intent(Intent.ACTION_GET_CONTENT);
436             intent.setType("audio/mp3");
437         } else if (preference == mPreferences[DCIM]) {
438             intent = new Intent(Intent.ACTION_VIEW);
439             intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
440             // TODO Create a Videos category, type = vnd.android.cursor.dir/video
441             intent.setType("vnd.android.cursor.dir/image");
442         } else if (preference == mPreferences[MISC]) {
443             Context context = getContext().getApplicationContext();
444             if (mMeasurement.getMiscSize() > 0) {
445                 intent = new Intent(context, MiscFilesHandler.class);
446                 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume);
447             }
448         }
449 
450         return intent;
451     }
452 }
453