• 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.ActivityManagerNative;
20 import android.app.ActivityThread;
21 import android.app.DownloadManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.UserInfo;
26 import android.content.res.Resources;
27 import android.hardware.usb.UsbManager;
28 import android.os.Environment;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.os.UserManager;
33 import android.os.storage.StorageManager;
34 import android.os.storage.StorageVolume;
35 import android.preference.Preference;
36 import android.preference.PreferenceCategory;
37 import android.text.format.Formatter;
38 
39 import com.android.settings.R;
40 import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails;
41 import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver;
42 import com.google.android.collect.Lists;
43 
44 import java.util.HashMap;
45 import java.util.Iterator;
46 import java.util.List;
47 
48 public class StorageVolumePreferenceCategory extends PreferenceCategory {
49     public static final String KEY_CACHE = "cache";
50 
51     private static final int ORDER_USAGE_BAR = -2;
52     private static final int ORDER_STORAGE_LOW = -1;
53 
54     /** Physical volume being measured, or {@code null} for internal. */
55     private final StorageVolume mVolume;
56     private final StorageMeasurement mMeasure;
57 
58     private final Resources mResources;
59     private final StorageManager mStorageManager;
60     private final UserManager mUserManager;
61 
62     private UsageBarPreference mUsageBarPreference;
63     private Preference mMountTogglePreference;
64     private Preference mFormatPreference;
65     private Preference mStorageLow;
66 
67     private StorageItemPreference mItemTotal;
68     private StorageItemPreference mItemAvailable;
69     private StorageItemPreference mItemApps;
70     private StorageItemPreference mItemDcim;
71     private StorageItemPreference mItemMusic;
72     private StorageItemPreference mItemDownloads;
73     private StorageItemPreference mItemCache;
74     private StorageItemPreference mItemMisc;
75     private List<StorageItemPreference> mItemUsers = Lists.newArrayList();
76 
77     private boolean mUsbConnected;
78     private String mUsbFunction;
79 
80     private long mTotalSize;
81 
82     private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
83     private static final int MSG_UI_UPDATE_DETAILS = 2;
84 
85     private Handler mUpdateHandler = new Handler() {
86         @Override
87         public void handleMessage(Message msg) {
88             switch (msg.what) {
89                 case MSG_UI_UPDATE_APPROXIMATE: {
90                     final long[] size = (long[]) msg.obj;
91                     updateApproximate(size[0], size[1]);
92                     break;
93                 }
94                 case MSG_UI_UPDATE_DETAILS: {
95                     final MeasurementDetails details = (MeasurementDetails) msg.obj;
96                     updateDetails(details);
97                     break;
98                 }
99             }
100         }
101     };
102 
103     /**
104      * Build category to summarize internal storage, including any emulated
105      * {@link StorageVolume}.
106      */
buildForInternal(Context context)107     public static StorageVolumePreferenceCategory buildForInternal(Context context) {
108         return new StorageVolumePreferenceCategory(context, null);
109     }
110 
111     /**
112      * Build category to summarize specific physical {@link StorageVolume}.
113      */
buildForPhysical( Context context, StorageVolume volume)114     public static StorageVolumePreferenceCategory buildForPhysical(
115             Context context, StorageVolume volume) {
116         return new StorageVolumePreferenceCategory(context, volume);
117     }
118 
StorageVolumePreferenceCategory(Context context, StorageVolume volume)119     private StorageVolumePreferenceCategory(Context context, StorageVolume volume) {
120         super(context);
121 
122         mVolume = volume;
123         mMeasure = StorageMeasurement.getInstance(context, volume);
124 
125         mResources = context.getResources();
126         mStorageManager = StorageManager.from(context);
127         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
128 
129         setTitle(volume != null ? volume.getDescription(context)
130                 : context.getText(R.string.internal_storage));
131     }
132 
buildItem(int titleRes, int colorRes)133     private StorageItemPreference buildItem(int titleRes, int colorRes) {
134         return new StorageItemPreference(getContext(), titleRes, colorRes);
135     }
136 
init()137     public void init() {
138         final Context context = getContext();
139 
140         final UserInfo currentUser;
141         try {
142             currentUser = ActivityManagerNative.getDefault().getCurrentUser();
143         } catch (RemoteException e) {
144             throw new RuntimeException("Failed to get current user");
145         }
146 
147         final List<UserInfo> otherUsers = getUsersExcluding(currentUser);
148         final boolean showUsers = mVolume == null && otherUsers.size() > 0;
149 
150         mUsageBarPreference = new UsageBarPreference(context);
151         mUsageBarPreference.setOrder(ORDER_USAGE_BAR);
152         addPreference(mUsageBarPreference);
153 
154         mItemTotal = buildItem(R.string.memory_size, 0);
155         mItemAvailable = buildItem(R.string.memory_available, R.color.memory_avail);
156         addPreference(mItemTotal);
157         addPreference(mItemAvailable);
158 
159         mItemApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage);
160         mItemDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim);
161         mItemMusic = buildItem(R.string.memory_music_usage, R.color.memory_music);
162         mItemDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads);
163         mItemCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache);
164         mItemMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc);
165 
166         mItemCache.setKey(KEY_CACHE);
167 
168         final boolean showDetails = mVolume == null || mVolume.isPrimary();
169         if (showDetails) {
170             if (showUsers) {
171                 addPreference(new PreferenceHeader(context, currentUser.name));
172             }
173 
174             addPreference(mItemApps);
175             addPreference(mItemDcim);
176             addPreference(mItemMusic);
177             addPreference(mItemDownloads);
178             addPreference(mItemCache);
179             addPreference(mItemMisc);
180 
181             if (showUsers) {
182                 addPreference(new PreferenceHeader(context, R.string.storage_other_users));
183 
184                 int count = 0;
185                 for (UserInfo info : otherUsers) {
186                     final int colorRes = count++ % 2 == 0 ? R.color.memory_user_light
187                             : R.color.memory_user_dark;
188                     final StorageItemPreference userPref = new StorageItemPreference(
189                             getContext(), info.name, colorRes, info.id);
190                     mItemUsers.add(userPref);
191                     addPreference(userPref);
192                 }
193             }
194         }
195 
196         final boolean isRemovable = mVolume != null ? mVolume.isRemovable() : false;
197         // Always create the preference since many code rely on it existing
198         mMountTogglePreference = new Preference(context);
199         if (isRemovable) {
200             mMountTogglePreference.setTitle(R.string.sd_eject);
201             mMountTogglePreference.setSummary(R.string.sd_eject_summary);
202             addPreference(mMountTogglePreference);
203         }
204 
205         // Only allow formatting of primary physical storage
206         // TODO: enable for non-primary volumes once MTP is fixed
207         final boolean allowFormat = mVolume != null ? mVolume.isPrimary() : false;
208         if (allowFormat) {
209             mFormatPreference = new Preference(context);
210             mFormatPreference.setTitle(R.string.sd_format);
211             mFormatPreference.setSummary(R.string.sd_format_summary);
212             addPreference(mFormatPreference);
213         }
214 
215         final IPackageManager pm = ActivityThread.getPackageManager();
216         try {
217             if (pm.isStorageLow()) {
218                 mStorageLow = new Preference(context);
219                 mStorageLow.setOrder(ORDER_STORAGE_LOW);
220                 mStorageLow.setTitle(R.string.storage_low_title);
221                 mStorageLow.setSummary(R.string.storage_low_summary);
222                 addPreference(mStorageLow);
223             } else if (mStorageLow != null) {
224                 removePreference(mStorageLow);
225                 mStorageLow = null;
226             }
227         } catch (RemoteException e) {
228         }
229     }
230 
getStorageVolume()231     public StorageVolume getStorageVolume() {
232         return mVolume;
233     }
234 
updatePreferencesFromState()235     private void updatePreferencesFromState() {
236         // Only update for physical volumes
237         if (mVolume == null) return;
238 
239         mMountTogglePreference.setEnabled(true);
240 
241         final String state = mStorageManager.getVolumeState(mVolume.getPath());
242 
243         if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
244             mItemAvailable.setTitle(R.string.memory_available_read_only);
245             if (mFormatPreference != null) {
246                 removePreference(mFormatPreference);
247             }
248         } else {
249             mItemAvailable.setTitle(R.string.memory_available);
250         }
251 
252         if (Environment.MEDIA_MOUNTED.equals(state)
253                 || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
254             mMountTogglePreference.setEnabled(true);
255             mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
256             mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary));
257         } else {
258             if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
259                     || Environment.MEDIA_UNMOUNTABLE.equals(state)) {
260                 mMountTogglePreference.setEnabled(true);
261                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
262                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary));
263             } else {
264                 mMountTogglePreference.setEnabled(false);
265                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
266                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary));
267             }
268 
269             removePreference(mUsageBarPreference);
270             removePreference(mItemTotal);
271             removePreference(mItemAvailable);
272             if (mFormatPreference != null) {
273                 removePreference(mFormatPreference);
274             }
275         }
276 
277         if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) ||
278                 UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) {
279             mMountTogglePreference.setEnabled(false);
280             if (Environment.MEDIA_MOUNTED.equals(state)
281                     || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
282                 mMountTogglePreference.setSummary(
283                         mResources.getString(R.string.mtp_ptp_mode_summary));
284             }
285 
286             if (mFormatPreference != null) {
287                 mFormatPreference.setEnabled(false);
288                 mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary));
289             }
290         } else if (mFormatPreference != null) {
291             mFormatPreference.setEnabled(true);
292             mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary));
293         }
294     }
295 
updateApproximate(long totalSize, long availSize)296     public void updateApproximate(long totalSize, long availSize) {
297         mItemTotal.setSummary(formatSize(totalSize));
298         mItemAvailable.setSummary(formatSize(availSize));
299 
300         mTotalSize = totalSize;
301 
302         final long usedSize = totalSize - availSize;
303 
304         mUsageBarPreference.clear();
305         mUsageBarPreference.addEntry(0, usedSize / (float) totalSize, android.graphics.Color.GRAY);
306         mUsageBarPreference.commit();
307 
308         updatePreferencesFromState();
309     }
310 
totalValues(HashMap<String, Long> map, String... keys)311     private static long totalValues(HashMap<String, Long> map, String... keys) {
312         long total = 0;
313         for (String key : keys) {
314             if (map.containsKey(key)) {
315                 total += map.get(key);
316             }
317         }
318         return total;
319     }
320 
updateDetails(MeasurementDetails details)321     public void updateDetails(MeasurementDetails details) {
322         final boolean showDetails = mVolume == null || mVolume.isPrimary();
323         if (!showDetails) return;
324 
325         // Count caches as available space, since system manages them
326         mItemTotal.setSummary(formatSize(details.totalSize));
327         mItemAvailable.setSummary(formatSize(details.availSize));
328 
329         mUsageBarPreference.clear();
330 
331         updatePreference(mItemApps, details.appsSize);
332 
333         final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
334                 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
335         updatePreference(mItemDcim, dcimSize);
336 
337         final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
338                 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
339                 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
340         updatePreference(mItemMusic, musicSize);
341 
342         final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
343         updatePreference(mItemDownloads, downloadsSize);
344 
345         updatePreference(mItemCache, details.cacheSize);
346         updatePreference(mItemMisc, details.miscSize);
347 
348         for (StorageItemPreference userPref : mItemUsers) {
349             final long userSize = details.usersSize.get(userPref.userHandle);
350             updatePreference(userPref, userSize);
351         }
352 
353         mUsageBarPreference.commit();
354     }
355 
updatePreference(StorageItemPreference pref, long size)356     private void updatePreference(StorageItemPreference pref, long size) {
357         if (size > 0) {
358             pref.setSummary(formatSize(size));
359             final int order = pref.getOrder();
360             mUsageBarPreference.addEntry(order, size / (float) mTotalSize, pref.color);
361         } else {
362             removePreference(pref);
363         }
364     }
365 
measure()366     private void measure() {
367         mMeasure.invalidate();
368         mMeasure.measure();
369     }
370 
onResume()371     public void onResume() {
372         mMeasure.setReceiver(mReceiver);
373         measure();
374     }
375 
onStorageStateChanged()376     public void onStorageStateChanged() {
377         measure();
378     }
379 
onUsbStateChanged(boolean isUsbConnected, String usbFunction)380     public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) {
381         mUsbConnected = isUsbConnected;
382         mUsbFunction = usbFunction;
383         measure();
384     }
385 
onMediaScannerFinished()386     public void onMediaScannerFinished() {
387         measure();
388     }
389 
onCacheCleared()390     public void onCacheCleared() {
391         measure();
392     }
393 
onPause()394     public void onPause() {
395         mMeasure.cleanUp();
396     }
397 
formatSize(long size)398     private String formatSize(long size) {
399         return Formatter.formatFileSize(getContext(), size);
400     }
401 
402     private MeasurementReceiver mReceiver = new MeasurementReceiver() {
403         @Override
404         public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) {
405             mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] {
406                     totalSize, availSize }).sendToTarget();
407         }
408 
409         @Override
410         public void updateDetails(StorageMeasurement meas, MeasurementDetails details) {
411             mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget();
412         }
413     };
414 
mountToggleClicked(Preference preference)415     public boolean mountToggleClicked(Preference preference) {
416         return preference == mMountTogglePreference;
417     }
418 
intentForClick(Preference pref)419     public Intent intentForClick(Preference pref) {
420         Intent intent = null;
421 
422         // TODO The current "delete" story is not fully handled by the respective applications.
423         // When it is done, make sure the intent types below are correct.
424         // If that cannot be done, remove these intents.
425         final String key = pref.getKey();
426         if (pref == mFormatPreference) {
427             intent = new Intent(Intent.ACTION_VIEW);
428             intent.setClass(getContext(), com.android.settings.MediaFormat.class);
429             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume);
430         } else if (pref == mItemApps) {
431             intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
432             intent.setClass(getContext(),
433                     com.android.settings.Settings.ManageApplicationsActivity.class);
434         } else if (pref == mItemDownloads) {
435             intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
436                     DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
437         } else if (pref == mItemMusic) {
438             intent = new Intent(Intent.ACTION_GET_CONTENT);
439             intent.setType("audio/mp3");
440         } else if (pref == mItemDcim) {
441             intent = new Intent(Intent.ACTION_VIEW);
442             intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
443             // TODO Create a Videos category, type = vnd.android.cursor.dir/video
444             intent.setType("vnd.android.cursor.dir/image");
445         } else if (pref == mItemMisc) {
446             Context context = getContext().getApplicationContext();
447             intent = new Intent(context, MiscFilesHandler.class);
448             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume);
449         }
450 
451         return intent;
452     }
453 
454     public static class PreferenceHeader extends Preference {
PreferenceHeader(Context context, int titleRes)455         public PreferenceHeader(Context context, int titleRes) {
456             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
457             setTitle(titleRes);
458         }
459 
PreferenceHeader(Context context, CharSequence title)460         public PreferenceHeader(Context context, CharSequence title) {
461             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
462             setTitle(title);
463         }
464 
465         @Override
isEnabled()466         public boolean isEnabled() {
467             return false;
468         }
469     }
470 
471     /**
472      * Return list of other users, excluding the current user.
473      */
getUsersExcluding(UserInfo excluding)474     private List<UserInfo> getUsersExcluding(UserInfo excluding) {
475         final List<UserInfo> users = mUserManager.getUsers();
476         final Iterator<UserInfo> i = users.iterator();
477         while (i.hasNext()) {
478             if (i.next().id == excluding.id) {
479                 i.remove();
480             }
481         }
482         return users;
483     }
484 }
485