• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.storage;
18 
19 import android.app.Dialog;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.os.AsyncTask;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.os.storage.DiskInfo;
26 import android.os.storage.StorageManager;
27 import android.os.storage.VolumeInfo;
28 import android.os.storage.VolumeRecord;
29 import android.text.TextUtils;
30 import android.text.format.Formatter;
31 import android.util.Log;
32 import android.widget.Toast;
33 
34 import androidx.appcompat.app.AlertDialog;
35 import androidx.fragment.app.Fragment;
36 
37 import com.android.settings.R;
38 import com.android.settings.core.SubSettingLauncher;
39 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
40 import com.android.settings.deviceinfo.PrivateVolumeForget;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.stream.Collectors;
45 
46 /** Storage utilities */
47 public class StorageUtils {
48 
49     private static final String TAG = "StorageUtils";
50 
51     /**
52      * Collects and returns all kinds of StorageEntry which will show in Storage Settings.
53      */
getAllStorageEntries(Context context, StorageManager storageManager)54     public static List<StorageEntry> getAllStorageEntries(Context context,
55             StorageManager storageManager) {
56         final List<StorageEntry> storageEntries = new ArrayList<>();
57         storageEntries.addAll(storageManager.getVolumes().stream()
58                 .filter(volumeInfo -> isStorageSettingsInterestedVolume(volumeInfo))
59                 .map(volumeInfo -> new StorageEntry(context, volumeInfo))
60                 .collect(Collectors.toList()));
61         storageEntries.addAll(storageManager.getDisks().stream()
62                 .filter(disk -> isDiskUnsupported(disk))
63                 .map(disk -> new StorageEntry(disk))
64                 .collect(Collectors.toList()));
65         storageEntries.addAll(storageManager.getVolumeRecords().stream()
66                 .filter(volumeRecord -> isVolumeRecordMissed(storageManager, volumeRecord))
67                 .map(volumeRecord -> new StorageEntry(volumeRecord))
68                 .collect(Collectors.toList()));
69         return storageEntries;
70     }
71 
72     /**
73      * Returns true if the volumeInfo may be displayed in Storage Settings.
74      */
isStorageSettingsInterestedVolume(VolumeInfo volumeInfo)75     public static boolean isStorageSettingsInterestedVolume(VolumeInfo volumeInfo) {
76         switch (volumeInfo.getType()) {
77             case VolumeInfo.TYPE_PRIVATE:
78             case VolumeInfo.TYPE_PUBLIC:
79             case VolumeInfo.TYPE_STUB:
80                 return true;
81             default:
82                 return false;
83         }
84     }
85 
86     /**
87      * VolumeRecord is a metadata of VolumeInfo, this is the case where a VolumeInfo is missing.
88      * (e.g., internal SD card is removed.)
89      */
isVolumeRecordMissed(StorageManager storageManager, VolumeRecord volumeRecord)90     public static boolean isVolumeRecordMissed(StorageManager storageManager,
91             VolumeRecord volumeRecord) {
92         return volumeRecord.getType() == VolumeInfo.TYPE_PRIVATE
93                 && storageManager.findVolumeByUuid(volumeRecord.getFsUuid()) == null;
94     }
95 
96     /**
97      * A unsupported disk is the disk of problem format, android is not able to mount automatically.
98      */
isDiskUnsupported(DiskInfo disk)99     public static boolean isDiskUnsupported(DiskInfo disk) {
100         return disk.volumeCount == 0 && disk.size > 0;
101     }
102 
103     /** Launches the fragment to forget a specified missing volume record. */
launchForgetMissingVolumeRecordFragment(Context context, StorageEntry storageEntry)104     public static void launchForgetMissingVolumeRecordFragment(Context context,
105             StorageEntry storageEntry) {
106         if (storageEntry == null || !storageEntry.isVolumeRecordMissed()) {
107             return;
108         }
109 
110         final Bundle args = new Bundle();
111         args.putString(VolumeRecord.EXTRA_FS_UUID, storageEntry.getFsUuid());
112         new SubSettingLauncher(context)
113                 .setDestination(PrivateVolumeForget.class.getCanonicalName())
114                 .setTitleRes(R.string.storage_menu_forget)
115                 .setSourceMetricsCategory(SettingsEnums.SETTINGS_STORAGE_CATEGORY)
116                 .setArguments(args)
117                 .launch();
118     }
119 
120     /** Returns size label of changing units. (e.g., 1kB, 2MB, 3GB) */
getStorageSizeLabel(Context context, long bytes)121     public static String getStorageSizeLabel(Context context, long bytes) {
122         final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(),
123                 bytes, Formatter.FLAG_SHORTER);
124         return TextUtils.expandTemplate(context.getText(R.string.storage_size_large),
125                 result.value, result.units).toString();
126     }
127 
128     /** An AsyncTask to unmount a specified volume. */
129     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
130         private final Context mContext;
131         private final StorageManager mStorageManager;
132         private final String mVolumeId;
133         private final String mDescription;
134 
UnmountTask(Context context, VolumeInfo volume)135         public UnmountTask(Context context, VolumeInfo volume) {
136             mContext = context.getApplicationContext();
137             mStorageManager = mContext.getSystemService(StorageManager.class);
138             mVolumeId = volume.getId();
139             mDescription = mStorageManager.getBestVolumeDescription(volume);
140         }
141 
142         @Override
doInBackground(Void... params)143         protected Exception doInBackground(Void... params) {
144             try {
145                 mStorageManager.unmount(mVolumeId);
146                 return null;
147             } catch (Exception e) {
148                 return e;
149             }
150         }
151 
152         @Override
onPostExecute(Exception e)153         protected void onPostExecute(Exception e) {
154             if (e == null) {
155                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
156                         mDescription), Toast.LENGTH_SHORT).show();
157             } else {
158                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
159                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
160                         mDescription), Toast.LENGTH_SHORT).show();
161             }
162         }
163     }
164 
165     /** An AsyncTask to mount a specified volume. */
166     public static class MountTask extends AsyncTask<Void, Void, Exception> {
167         private final Context mContext;
168         private final StorageManager mStorageManager;
169         private final String mVolumeId;
170         private final String mDescription;
171 
MountTask(Context context, VolumeInfo volume)172         public MountTask(Context context, VolumeInfo volume) {
173             mContext = context.getApplicationContext();
174             mStorageManager = mContext.getSystemService(StorageManager.class);
175             mVolumeId = volume.getId();
176             mDescription = mStorageManager.getBestVolumeDescription(volume);
177         }
178 
179         @Override
doInBackground(Void... params)180         protected Exception doInBackground(Void... params) {
181             try {
182                 mStorageManager.mount(mVolumeId);
183                 return null;
184             } catch (Exception e) {
185                 return e;
186             }
187         }
188 
189         @Override
onPostExecute(Exception e)190         protected void onPostExecute(Exception e) {
191             if (e == null) {
192                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
193                         mDescription), Toast.LENGTH_SHORT).show();
194             } else {
195                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
196                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
197                         mDescription), Toast.LENGTH_SHORT).show();
198             }
199         }
200     }
201 
202     /* Shows information about system storage. */
203     public static class SystemInfoFragment extends InstrumentedDialogFragment {
204         /** Shows the fragment. */
show(Fragment parent)205         public static void show(Fragment parent) {
206             if (!parent.isAdded()) return;
207 
208             final SystemInfoFragment dialog = new SystemInfoFragment();
209             dialog.setTargetFragment(parent, 0);
210             dialog.show(parent.getFragmentManager(), "systemInfo");
211         }
212 
213         @Override
getMetricsCategory()214         public int getMetricsCategory() {
215             return SettingsEnums.DIALOG_STORAGE_SYSTEM_INFO;
216         }
217 
218         @Override
onCreateDialog(Bundle savedInstanceState)219         public Dialog onCreateDialog(Bundle savedInstanceState) {
220             return new AlertDialog.Builder(getActivity())
221                     .setMessage(getContext().getString(R.string.storage_detail_dialog_system,
222                             Build.VERSION.RELEASE_OR_CODENAME))
223                     .setPositiveButton(android.R.string.ok, null)
224                     .create();
225         }
226     }
227 }
228