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.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
     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 
    108     private void addCategory(StorageVolumePreferenceCategory category) {
    109         mCategories.add(category);
    110         getPreferenceScreen().addPreference(category);
    111         category.init();
    112     }
    113 
    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
    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
    154     public void onPause() {
    155         super.onPause();
    156         getActivity().unregisterReceiver(mMediaScannerReceiver);
    157         for (StorageVolumePreferenceCategory category : mCategories) {
    158             category.onPause();
    159         }
    160     }
    161 
    162     @Override
    163     public void onDestroy() {
    164         if (mStorageManager != null && mStorageListener != null) {
    165             mStorageManager.unregisterListener(mStorageListener);
    166         }
    167         super.onDestroy();
    168     }
    169 
    170     @Override
    171     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    172         inflater.inflate(R.menu.storage, menu);
    173     }
    174 
    175     @Override
    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
    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 
    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
    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
    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