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