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