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