1 /* 2 * Copyright (C) 2022 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 android.os.storage.cts; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.os.ParcelFileDescriptor; 22 import android.util.Log; 23 24 import androidx.test.platform.app.InstrumentationRegistry; 25 26 import com.google.common.collect.Iterables; 27 28 import java.io.BufferedReader; 29 import java.io.FileInputStream; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.nio.charset.StandardCharsets; 33 import java.util.ArrayList; 34 import java.util.List; 35 import java.util.concurrent.TimeUnit; 36 import java.util.concurrent.TimeoutException; 37 import java.util.function.Supplier; 38 39 final class StorageManagerHelper { 40 41 private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20); 42 private static final long POLLING_SLEEP_MILLIS = 100; 43 44 /** 45 * Creates a virtual disk that simulates SDCard on a device. It is 46 * mounted as a public visible disk. 47 * @return the volume name of the disk just created 48 * @throws Exception, if the volume could not be created 49 */ createSDCardVirtualDisk()50 static String createSDCardVirtualDisk() throws Exception { 51 return createDiskAndGetVolumeName(true); 52 } 53 /** 54 * Creates a virtual disk that simulates USB on a device. It is 55 * mounted as a public invisible disk. 56 * @return the volume name of the disk just created 57 * @throws Exception, if the volume could not be created 58 */ createUSBVirtualDisk()59 static String createUSBVirtualDisk() throws Exception { 60 return createDiskAndGetVolumeName(false); 61 } 62 63 /** 64 * Removes the simulated disk 65 */ removeVirtualDisk()66 static void removeVirtualDisk() throws Exception { 67 executeShellCommand("sm set-virtual-disk false"); 68 //sleep to make sure that it is unmounted 69 Thread.sleep(5000); 70 } 71 72 /** 73 * Create a public volume for testing and only return the one newly created as the volumeName. 74 */ createDiskAndGetVolumeName(boolean visible)75 public static String createDiskAndGetVolumeName(boolean visible) throws Exception { 76 //remove any existing volume that was mounted before 77 removeVirtualDisk(); 78 String existingPublicVolume = getPublicVolumeExcluding(null); 79 executeShellCommand("sm set-force-adoptable " + (visible ? "on" : "off")); 80 executeShellCommand("sm set-virtual-disk true"); 81 Thread.sleep(10000); 82 pollForCondition(StorageManagerHelper::partitionDisks, 83 "Could not create public volume in time"); 84 return getPublicVolumeExcluding(existingPublicVolume); 85 } 86 partitionDisks()87 private static boolean partitionDisks() { 88 try { 89 List<String> diskNames = executeShellCommand("sm list-disks"); 90 if (!diskNames.isEmpty()) { 91 executeShellCommand("sm partition " + Iterables.getLast(diskNames) + " public"); 92 return true; 93 } 94 } catch (Exception ignored) { 95 //ignored 96 } 97 return false; 98 } 99 pollForCondition(Supplier<Boolean> condition, String errorMessage)100 private static void pollForCondition(Supplier<Boolean> condition, String errorMessage) 101 throws Exception { 102 Thread.sleep(2000); 103 for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) { 104 if (condition.get()) { 105 return; 106 } 107 Thread.sleep(POLLING_SLEEP_MILLIS); 108 } 109 throw new TimeoutException(errorMessage); 110 } 111 getPublicVolumeExcluding(String excludingVolume)112 private static String getPublicVolumeExcluding(String excludingVolume) throws Exception { 113 114 List<String> volumes = executeShellCommand("sm list-volumes"); 115 // list volumes will result in something like 116 // private mounted null 117 // public:7,281 mounted 3080-17E8 118 // emulated;0 mounted null 119 // and we are interested in 3080-17E8 120 for (String volume: volumes) { 121 if (volume.contains("public") 122 && (excludingVolume == null || !volume.contains(excludingVolume))) { 123 //public:7,281 mounted 3080-17E8 124 String[] splits = volume.split(" "); 125 //Return the last snippet, that is 3080-17E8 126 return splits[splits.length - 1]; 127 } 128 } 129 return null; 130 } 131 isAdoptableStorageSupported(Context context)132 public static boolean isAdoptableStorageSupported(Context context) throws Exception { 133 return hasAdoptableStorageFeature(context) || hasAdoptableStorageFstab(); 134 } 135 hasAdoptableStorageFstab()136 private static boolean hasAdoptableStorageFstab() throws Exception { 137 List<String> hasAdoptable = executeShellCommand("sm has-adoptable"); 138 if (hasAdoptable.isEmpty()) { 139 return false; 140 } 141 return Boolean.parseBoolean(hasAdoptable.get(0).trim()); 142 } 143 hasAdoptableStorageFeature(Context context)144 private static boolean hasAdoptableStorageFeature(Context context) throws Exception { 145 return context.getPackageManager().hasSystemFeature( 146 PackageManager.FEATURE_ADOPTABLE_STORAGE); 147 } 148 executeShellCommand(String command)149 private static List<String> executeShellCommand(String command) throws Exception { 150 final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation() 151 .getUiAutomation().executeShellCommand(command); 152 BufferedReader br = null; 153 try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) { 154 br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 155 String str = null; 156 List<String> output = new ArrayList<>(); 157 while ((str = br.readLine()) != null) { 158 output.add(str); 159 } 160 return output; 161 } finally { 162 if (br != null) { 163 closeQuietly(br); 164 } 165 closeQuietly(pfd); 166 } 167 } 168 closeQuietly(AutoCloseable closeable)169 private static void closeQuietly(AutoCloseable closeable) { 170 if (closeable != null) { 171 172 try { 173 closeable.close(); 174 } catch (RuntimeException rethrown) { 175 throw rethrown; 176 } catch (Exception ignored) { 177 Log.w("StorageManagerHelper", ignored.getMessage()); 178 } 179 } 180 } 181 } 182