• 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.content.Intent;
23 import android.content.pm.ResolveInfo;
24 import android.graphics.drawable.Drawable;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.storage.DiskInfo;
28 import android.os.storage.StorageManager;
29 import android.os.storage.VolumeInfo;
30 import android.os.storage.VolumeRecord;
31 import android.text.SpannableString;
32 import android.text.TextUtils;
33 import android.text.format.Formatter;
34 import android.text.style.TtsSpan;
35 import android.util.Log;
36 import android.widget.Toast;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.appcompat.app.AlertDialog;
41 import androidx.fragment.app.Fragment;
42 
43 import com.android.settings.R;
44 import com.android.settings.Utils;
45 import com.android.settings.core.SubSettingLauncher;
46 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
47 import com.android.settings.deviceinfo.PrivateVolumeForget;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.stream.Collectors;
52 
53 /** Storage utilities */
54 public class StorageUtils {
55 
56     private static final String TAG = "StorageUtils";
57 
58     /**
59      * Collects and returns all kinds of StorageEntry which will show in Storage Settings.
60      */
getAllStorageEntries(Context context, StorageManager storageManager)61     public static List<StorageEntry> getAllStorageEntries(Context context,
62             StorageManager storageManager) {
63         final List<StorageEntry> storageEntries = new ArrayList<>();
64         storageEntries.addAll(storageManager.getVolumes().stream()
65                 .filter(volumeInfo -> isStorageSettingsInterestedVolume(volumeInfo))
66                 .map(volumeInfo -> new StorageEntry(context, volumeInfo))
67                 .collect(Collectors.toList()));
68         storageEntries.addAll(storageManager.getDisks().stream()
69                 .filter(disk -> isDiskUnsupported(disk))
70                 .map(disk -> new StorageEntry(disk))
71                 .collect(Collectors.toList()));
72         storageEntries.addAll(storageManager.getVolumeRecords().stream()
73                 .filter(volumeRecord -> isVolumeRecordMissed(storageManager, volumeRecord))
74                 .map(volumeRecord -> new StorageEntry(volumeRecord))
75                 .collect(Collectors.toList()));
76         return storageEntries;
77     }
78 
79     /**
80      * Returns true if the volumeInfo may be displayed in Storage Settings.
81      */
isStorageSettingsInterestedVolume(VolumeInfo volumeInfo)82     public static boolean isStorageSettingsInterestedVolume(VolumeInfo volumeInfo) {
83         switch (volumeInfo.getType()) {
84             case VolumeInfo.TYPE_PRIVATE:
85             case VolumeInfo.TYPE_PUBLIC:
86             case VolumeInfo.TYPE_STUB:
87                 return true;
88             default:
89                 return false;
90         }
91     }
92 
93     /**
94      * VolumeRecord is a metadata of VolumeInfo, this is the case where a VolumeInfo is missing.
95      * (e.g., internal SD card is removed.)
96      */
isVolumeRecordMissed(StorageManager storageManager, VolumeRecord volumeRecord)97     public static boolean isVolumeRecordMissed(StorageManager storageManager,
98             VolumeRecord volumeRecord) {
99         return volumeRecord.getType() == VolumeInfo.TYPE_PRIVATE
100                 && storageManager.findVolumeByUuid(volumeRecord.getFsUuid()) == null;
101     }
102 
103     /**
104      * A unsupported disk is the disk of problem format, android is not able to mount automatically.
105      */
isDiskUnsupported(DiskInfo disk)106     public static boolean isDiskUnsupported(DiskInfo disk) {
107         return disk.volumeCount == 0 && disk.size > 0;
108     }
109 
110     /** Launches the fragment to forget a specified missing volume record. */
launchForgetMissingVolumeRecordFragment(Context context, StorageEntry storageEntry)111     public static void launchForgetMissingVolumeRecordFragment(Context context,
112             StorageEntry storageEntry) {
113         if (storageEntry == null || !storageEntry.isVolumeRecordMissed()) {
114             return;
115         }
116 
117         final Bundle args = new Bundle();
118         args.putString(VolumeRecord.EXTRA_FS_UUID, storageEntry.getFsUuid());
119         new SubSettingLauncher(context)
120                 .setDestination(PrivateVolumeForget.class.getCanonicalName())
121                 .setTitleRes(R.string.storage_menu_forget)
122                 .setSourceMetricsCategory(SettingsEnums.SETTINGS_STORAGE_CATEGORY)
123                 .setArguments(args)
124                 .launch();
125     }
126 
127     /** Returns size label of changing units. (e.g., 1kB, 2MB, 3GB) */
getStorageSizeLabel(@onNull Context context, long bytes)128     public static @Nullable CharSequence getStorageSizeLabel(@NonNull Context context, long bytes) {
129         final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(),
130                 bytes, Formatter.FLAG_SHORTER);
131         String storageSize = TextUtils.expandTemplate(context.getText(R.string.storage_size_large),
132                 result.value, result.units).toString();
133 
134         // If storage size is less than 1KB, use TtsSpan to add additional metadata for
135         // text-to-speech engines.
136         if (bytes < 1024) {
137             TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(bytes).setUnit("byte").build();
138             SpannableString phraseSpannable = new SpannableString(storageSize);
139             phraseSpannable.setSpan(ttsSpan, 0, phraseSpannable.length(), 0);
140             return phraseSpannable;
141         }
142 
143         return storageSize;
144     }
145 
146     /** An AsyncTask to unmount a specified volume. */
147     public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
148         private final Context mContext;
149         private final StorageManager mStorageManager;
150         private final String mVolumeId;
151         private final String mDescription;
152 
UnmountTask(Context context, VolumeInfo volume)153         public UnmountTask(Context context, VolumeInfo volume) {
154             mContext = context.getApplicationContext();
155             mStorageManager = mContext.getSystemService(StorageManager.class);
156             mVolumeId = volume.getId();
157             mDescription = mStorageManager.getBestVolumeDescription(volume);
158         }
159 
160         @Override
doInBackground(Void... params)161         protected Exception doInBackground(Void... params) {
162             try {
163                 mStorageManager.unmount(mVolumeId);
164                 return null;
165             } catch (Exception e) {
166                 return e;
167             }
168         }
169 
170         @Override
onPostExecute(Exception e)171         protected void onPostExecute(Exception e) {
172             if (e == null) {
173                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
174                         mDescription), Toast.LENGTH_SHORT).show();
175             } else {
176                 Log.e(TAG, "Failed to unmount " + mVolumeId, e);
177                 Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
178                         mDescription), Toast.LENGTH_SHORT).show();
179             }
180         }
181     }
182 
183     /** An AsyncTask to mount a specified volume. */
184     public static class MountTask extends AsyncTask<Void, Void, Exception> {
185         private final Context mContext;
186         private final StorageManager mStorageManager;
187         private final String mVolumeId;
188         private final String mDescription;
189 
MountTask(Context context, VolumeInfo volume)190         public MountTask(Context context, VolumeInfo volume) {
191             mContext = context.getApplicationContext();
192             mStorageManager = mContext.getSystemService(StorageManager.class);
193             mVolumeId = volume.getId();
194             mDescription = mStorageManager.getBestVolumeDescription(volume);
195         }
196 
197         @Override
doInBackground(Void... params)198         protected Exception doInBackground(Void... params) {
199             try {
200                 mStorageManager.mount(mVolumeId);
201                 return null;
202             } catch (Exception e) {
203                 return e;
204             }
205         }
206 
207         @Override
onPostExecute(Exception e)208         protected void onPostExecute(Exception e) {
209             if (e == null) {
210                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
211                         mDescription), Toast.LENGTH_SHORT).show();
212             } else {
213                 Log.e(TAG, "Failed to mount " + mVolumeId, e);
214                 Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
215                         mDescription), Toast.LENGTH_SHORT).show();
216             }
217         }
218     }
219 
220     /** Shows information about system storage. */
221     public static class SystemInfoFragment extends InstrumentedDialogFragment {
222         /** Shows the fragment. */
show(@onNull Fragment parent)223         public static void show(@NonNull Fragment parent) {
224             if (!parent.isAdded()) return;
225 
226             final SystemInfoFragment dialog = new SystemInfoFragment();
227             dialog.setTargetFragment(parent, 0);
228             dialog.show(parent.getFragmentManager(), "systemInfo");
229         }
230 
231         @Override
getMetricsCategory()232         public int getMetricsCategory() {
233             return SettingsEnums.DIALOG_STORAGE_SYSTEM_INFO;
234         }
235 
236         @Override
onCreateDialog(Bundle savedInstanceState)237         public Dialog onCreateDialog(Bundle savedInstanceState) {
238             return new AlertDialog.Builder(getActivity())
239                     .setMessage(getContext().getString(R.string.storage_os_detail_dialog_system))
240                     .setPositiveButton(android.R.string.ok, null)
241                     .create();
242         }
243     }
244 
245     /** Shows information about temporary system files. */
246     public static class TemporaryFilesInfoFragment extends InstrumentedDialogFragment {
247         /** Shows the fragment. */
show(@onNull Fragment parent)248         public static void show(@NonNull Fragment parent) {
249             if (!parent.isAdded()) return;
250 
251             final TemporaryFilesInfoFragment dialog = new TemporaryFilesInfoFragment();
252             dialog.setTargetFragment(parent, 0);
253             dialog.show(parent.getFragmentManager(), "temporaryFilesInfo");
254         }
255 
256         @Override
getMetricsCategory()257         public int getMetricsCategory() {
258             return SettingsEnums.DIALOG_TEMPORARY_FILES_INFO;
259         }
260 
261         @Override
onCreateDialog(Bundle savedInstanceState)262         public Dialog onCreateDialog(Bundle savedInstanceState) {
263             return new AlertDialog.Builder(getActivity())
264                     .setMessage(getContext().getString(
265                             R.string.storage_other_files_detail_dialog_system))
266                     .setPositiveButton(android.R.string.ok, null)
267                     .create();
268         }
269     }
270 
271     /** Gets a summary which has a byte size information. */
getStorageSummary(Context context, int resId, long bytes)272     public static String getStorageSummary(Context context, int resId, long bytes) {
273         final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(),
274                 bytes, Formatter.FLAG_SHORTER);
275         return context.getString(resId, result.value, result.units);
276     }
277 
278     /** Gets icon for Preference of Free up space. */
getManageStorageIcon(Context context, int userId)279     public static Drawable getManageStorageIcon(Context context, int userId) {
280         ResolveInfo resolveInfo = context.getPackageManager().resolveActivityAsUser(
281                 new Intent(StorageManager.ACTION_MANAGE_STORAGE), 0 /* flags */, userId);
282         if (resolveInfo == null || resolveInfo.activityInfo == null) {
283             return null;
284         }
285 
286         return Utils.getBadgedIcon(context, resolveInfo.activityInfo.applicationInfo);
287     }
288 }
289