• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.AlertDialog;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.IPackageDataObserver;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.hardware.usb.UsbManager;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.UserManager;
37 import android.os.storage.IMountService;
38 import android.os.storage.StorageEventListener;
39 import android.os.storage.StorageManager;
40 import android.os.storage.StorageVolume;
41 import android.preference.Preference;
42 import android.preference.PreferenceActivity;
43 import android.preference.PreferenceScreen;
44 import android.util.Log;
45 import android.view.Menu;
46 import android.view.MenuInflater;
47 import android.view.MenuItem;
48 import android.widget.Toast;
49 
50 import com.android.settings.R;
51 import com.android.settings.SettingsPreferenceFragment;
52 import com.android.settings.Utils;
53 import com.google.android.collect.Lists;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 
58 /**
59  * Panel showing storage usage on disk for known {@link StorageVolume} returned
60  * by {@link StorageManager}. Calculates and displays usage of data types.
61  */
62 public class Memory extends SettingsPreferenceFragment {
63     private static final String TAG = "MemorySettings";
64 
65     private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
66 
67     private static final int DLG_CONFIRM_UNMOUNT = 1;
68     private static final int DLG_ERROR_UNMOUNT = 2;
69 
70     // The mountToggle Preference that has last been clicked.
71     // Assumes no two successive unmount event on 2 different volumes are performed before the first
72     // one's preference is disabled
73     private static Preference sLastClickedMountToggle;
74     private static String sClickedMountPoint;
75 
76     // Access using getMountService()
77     private IMountService mMountService;
78     private StorageManager mStorageManager;
79     private UsbManager mUsbManager;
80 
81     private ArrayList<StorageVolumePreferenceCategory> mCategories = Lists.newArrayList();
82 
83     @Override
onCreate(Bundle icicle)84     public void onCreate(Bundle icicle) {
85         super.onCreate(icicle);
86 
87         final Context context = getActivity();
88 
89         mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
90 
91         mStorageManager = StorageManager.from(context);
92         mStorageManager.registerListener(mStorageListener);
93 
94         addPreferencesFromResource(R.xml.device_info_memory);
95 
96         addCategory(StorageVolumePreferenceCategory.buildForInternal(context));
97 
98         final StorageVolume[] storageVolumes = mStorageManager.getVolumeList();
99         for (StorageVolume volume : storageVolumes) {
100             if (!volume.isEmulated()) {
101                 addCategory(StorageVolumePreferenceCategory.buildForPhysical(context, volume));
102             }
103         }
104 
105         setHasOptionsMenu(true);
106     }
107 
addCategory(StorageVolumePreferenceCategory category)108     private void addCategory(StorageVolumePreferenceCategory category) {
109         mCategories.add(category);
110         getPreferenceScreen().addPreference(category);
111         category.init();
112     }
113 
isMassStorageEnabled()114     private boolean isMassStorageEnabled() {
115         // Mass storage is enabled if primary volume supports it
116         final StorageVolume[] volumes = mStorageManager.getVolumeList();
117         final StorageVolume primary = StorageManager.getPrimaryVolume(volumes);
118         return primary != null && primary.allowMassStorage();
119     }
120 
121     @Override
onResume()122     public void onResume() {
123         super.onResume();
124         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
125         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
126         intentFilter.addDataScheme("file");
127         getActivity().registerReceiver(mMediaScannerReceiver, intentFilter);
128 
129         intentFilter = new IntentFilter();
130         intentFilter.addAction(UsbManager.ACTION_USB_STATE);
131         getActivity().registerReceiver(mMediaScannerReceiver, intentFilter);
132 
133         for (StorageVolumePreferenceCategory category : mCategories) {
134             category.onResume();
135         }
136     }
137 
138     StorageEventListener mStorageListener = new StorageEventListener() {
139         @Override
140         public void onStorageStateChanged(String path, String oldState, String newState) {
141             Log.i(TAG, "Received storage state changed notification that " + path +
142                     " changed state from " + oldState + " to " + newState);
143             for (StorageVolumePreferenceCategory category : mCategories) {
144                 final StorageVolume volume = category.getStorageVolume();
145                 if (volume != null && path.equals(volume.getPath())) {
146                     category.onStorageStateChanged();
147                     break;
148                 }
149             }
150         }
151     };
152 
153     @Override
onPause()154     public void onPause() {
155         super.onPause();
156         getActivity().unregisterReceiver(mMediaScannerReceiver);
157         for (StorageVolumePreferenceCategory category : mCategories) {
158             category.onPause();
159         }
160     }
161 
162     @Override
onDestroy()163     public void onDestroy() {
164         if (mStorageManager != null && mStorageListener != null) {
165             mStorageManager.unregisterListener(mStorageListener);
166         }
167         super.onDestroy();
168     }
169 
170     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)171     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
172         inflater.inflate(R.menu.storage, menu);
173     }
174 
175     @Override
onPrepareOptionsMenu(Menu menu)176     public void onPrepareOptionsMenu(Menu menu) {
177         final MenuItem usb = menu.findItem(R.id.storage_usb);
178         UserManager um = (UserManager)getActivity().getSystemService(Context.USER_SERVICE);
179         boolean usbItemVisible = !isMassStorageEnabled()
180                 && !um.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
181         usb.setVisible(usbItemVisible);
182     }
183 
184     @Override
onOptionsItemSelected(MenuItem item)185     public boolean onOptionsItemSelected(MenuItem item) {
186         switch (item.getItemId()) {
187             case R.id.storage_usb:
188                 if (getActivity() instanceof PreferenceActivity) {
189                     ((PreferenceActivity) getActivity()).startPreferencePanel(
190                             UsbSettings.class.getCanonicalName(),
191                             null,
192                             R.string.storage_title_usb, null,
193                             this, 0);
194                 } else {
195                     startFragment(this, UsbSettings.class.getCanonicalName(), -1, null);
196                 }
197                 return true;
198         }
199         return super.onOptionsItemSelected(item);
200     }
201 
getMountService()202     private synchronized IMountService getMountService() {
203        if (mMountService == null) {
204            IBinder service = ServiceManager.getService("mount");
205            if (service != null) {
206                mMountService = IMountService.Stub.asInterface(service);
207            } else {
208                Log.e(TAG, "Can't get mount service");
209            }
210        }
211        return mMountService;
212     }
213 
214     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)215     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
216         if (StorageVolumePreferenceCategory.KEY_CACHE.equals(preference.getKey())) {
217             ConfirmClearCacheFragment.show(this);
218             return true;
219         }
220 
221         for (StorageVolumePreferenceCategory category : mCategories) {
222             Intent intent = category.intentForClick(preference);
223             if (intent != null) {
224                 // Don't go across app boundary if monkey is running
225                 if (!Utils.isMonkeyRunning()) {
226                     startActivity(intent);
227                 }
228                 return true;
229             }
230 
231             final StorageVolume volume = category.getStorageVolume();
232             if (volume != null && category.mountToggleClicked(preference)) {
233                 sLastClickedMountToggle = preference;
234                 sClickedMountPoint = volume.getPath();
235                 String state = mStorageManager.getVolumeState(volume.getPath());
236                 if (Environment.MEDIA_MOUNTED.equals(state) ||
237                         Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
238                     unmount();
239                 } else {
240                     mount();
241                 }
242                 return true;
243             }
244         }
245 
246         return false;
247     }
248 
249     private final BroadcastReceiver mMediaScannerReceiver = new BroadcastReceiver() {
250         @Override
251         public void onReceive(Context context, Intent intent) {
252             String action = intent.getAction();
253             if (action.equals(UsbManager.ACTION_USB_STATE)) {
254                boolean isUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
255                String usbFunction = mUsbManager.getDefaultFunction();
256                for (StorageVolumePreferenceCategory category : mCategories) {
257                    category.onUsbStateChanged(isUsbConnected, usbFunction);
258                }
259             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
260                 for (StorageVolumePreferenceCategory category : mCategories) {
261                     category.onMediaScannerFinished();
262                 }
263             }
264         }
265     };
266 
267     @Override
onCreateDialog(int id)268     public Dialog onCreateDialog(int id) {
269         switch (id) {
270         case DLG_CONFIRM_UNMOUNT:
271                 return new AlertDialog.Builder(getActivity())
272                     .setTitle(R.string.dlg_confirm_unmount_title)
273                     .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
274                         public void onClick(DialogInterface dialog, int which) {
275                             doUnmount();
276                         }})
277                     .setNegativeButton(R.string.cancel, null)
278                     .setMessage(R.string.dlg_confirm_unmount_text)
279                     .create();
280         case DLG_ERROR_UNMOUNT:
281                 return new AlertDialog.Builder(getActivity())
282             .setTitle(R.string.dlg_error_unmount_title)
283             .setNeutralButton(R.string.dlg_ok, null)
284             .setMessage(R.string.dlg_error_unmount_text)
285             .create();
286         }
287         return null;
288     }
289 
290     private void doUnmount() {
291         // Present a toast here
292         Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
293         IMountService mountService = getMountService();
294         try {
295             sLastClickedMountToggle.setEnabled(false);
296             sLastClickedMountToggle.setTitle(getString(R.string.sd_ejecting_title));
297             sLastClickedMountToggle.setSummary(getString(R.string.sd_ejecting_summary));
298             mountService.unmountVolume(sClickedMountPoint, true, false);
299         } catch (RemoteException e) {
300             // Informative dialog to user that unmount failed.
301             showDialogInner(DLG_ERROR_UNMOUNT);
302         }
303     }
304 
305     private void showDialogInner(int id) {
306         removeDialog(id);
307         showDialog(id);
308     }
309 
310     private boolean hasAppsAccessingStorage() throws RemoteException {
311         IMountService mountService = getMountService();
312         int stUsers[] = mountService.getStorageUsers(sClickedMountPoint);
313         if (stUsers != null && stUsers.length > 0) {
314             return true;
315         }
316         // TODO FIXME Parameterize with mountPoint and uncomment.
317         // On HC-MR2, no apps can be installed on sd and the emulated internal storage is not
318         // removable: application cannot interfere with unmount
319         /*
320         ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
321         List<ApplicationInfo> list = am.getRunningExternalApplications();
322         if (list != null && list.size() > 0) {
323             return true;
324         }
325         */
326         // Better safe than sorry. Assume the storage is used to ask for confirmation.
327         return true;
328     }
329 
330     private void unmount() {
331         // Check if external media is in use.
332         try {
333            if (hasAppsAccessingStorage()) {
334                // Present dialog to user
335                showDialogInner(DLG_CONFIRM_UNMOUNT);
336            } else {
337                doUnmount();
338            }
339         } catch (RemoteException e) {
340             // Very unlikely. But present an error dialog anyway
341             Log.e(TAG, "Is MountService running?");
342             showDialogInner(DLG_ERROR_UNMOUNT);
343         }
344     }
345 
346     private void mount() {
347         IMountService mountService = getMountService();
348         try {
349             if (mountService != null) {
350                 mountService.mountVolume(sClickedMountPoint);
351             } else {
352                 Log.e(TAG, "Mount service is null, can't mount");
353             }
354         } catch (RemoteException ex) {
355             // Not much can be done
356         }
357     }
358 
359     private void onCacheCleared() {
360         for (StorageVolumePreferenceCategory category : mCategories) {
361             category.onCacheCleared();
362         }
363     }
364 
365     private static class ClearCacheObserver extends IPackageDataObserver.Stub {
366         private final Memory mTarget;
367         private int mRemaining;
368 
369         public ClearCacheObserver(Memory target, int remaining) {
370             mTarget = target;
371             mRemaining = remaining;
372         }
373 
374         @Override
375         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
376             synchronized (this) {
377                 if (--mRemaining == 0) {
378                     mTarget.onCacheCleared();
379                 }
380             }
381         }
382     }
383 
384     /**
385      * Dialog to request user confirmation before clearing all cache data.
386      */
387     public static class ConfirmClearCacheFragment extends DialogFragment {
388         public static void show(Memory parent) {
389             if (!parent.isAdded()) return;
390 
391             final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
392             dialog.setTargetFragment(parent, 0);
393             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
394         }
395 
396         @Override
397         public Dialog onCreateDialog(Bundle savedInstanceState) {
398             final Context context = getActivity();
399 
400             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
401             builder.setTitle(R.string.memory_clear_cache_title);
402             builder.setMessage(getString(R.string.memory_clear_cache_message));
403 
404             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
405                 @Override
406                 public void onClick(DialogInterface dialog, int which) {
407                     final Memory target = (Memory) getTargetFragment();
408                     final PackageManager pm = context.getPackageManager();
409                     final List<PackageInfo> infos = pm.getInstalledPackages(0);
410                     final ClearCacheObserver observer = new ClearCacheObserver(
411                             target, infos.size());
412                     for (PackageInfo info : infos) {
413                         pm.deleteApplicationCacheFiles(info.packageName, observer);
414                     }
415                 }
416             });
417             builder.setNegativeButton(android.R.string.cancel, null);
418 
419             return builder.create();
420         }
421     }
422 }
423