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.app.sdksandbox.hosttestutils; 18 19 import static org.junit.Assert.assertTrue; 20 21 import com.android.tradefed.log.LogUtil; 22 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 23 import com.android.tradefed.util.RunUtil; 24 25 import java.util.Arrays; 26 27 public class AdoptableStorageUtils { 28 29 private final BaseHostJUnit4Test mTest; 30 31 private String mDiskId; 32 AdoptableStorageUtils(BaseHostJUnit4Test test)33 public AdoptableStorageUtils(BaseHostJUnit4Test test) { 34 mTest = test; 35 } 36 isAdoptableStorageSupported()37 public boolean isAdoptableStorageSupported() throws Exception { 38 boolean hasFeature = 39 mTest.getDevice().hasFeature("feature:android.software.adoptable_storage"); 40 boolean hasFstab = 41 Boolean.parseBoolean( 42 mTest.getDevice().executeShellCommand("sm has-adoptable").trim()); 43 return hasFeature || hasFstab; 44 } 45 46 // Creates a new volume in adoptable storage and returns its uuid createNewVolume()47 public String createNewVolume() throws Exception { 48 mDiskId = getAdoptionDisk(); 49 assertEmpty(mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " private")); 50 final LocalVolumeInfo vol = getAdoptionVolume(); 51 return vol.uuid; 52 } 53 54 /** 55 * Enable a virtual disk to give us the best shot at being able to pass various tests. This 56 * helps verify devices that may not currently have an SD card inserted. 57 */ enableVirtualDisk()58 public void enableVirtualDisk() throws Exception { 59 if (isAdoptableStorageSupported()) { 60 mTest.getDevice().executeShellCommand("sm set-virtual-disk true"); 61 62 // Ensure virtual disk is mounted. 63 int attempt = 0; 64 boolean hasVirtualDisk = false; 65 String result = ""; 66 while (!hasVirtualDisk && attempt++ < 50) { 67 RunUtil.getDefault().sleep(1000); 68 result = mTest.getDevice().executeShellCommand("sm list-disks adoptable").trim(); 69 hasVirtualDisk = result.startsWith("disk:"); 70 } 71 assertTrue("Virtual disk is not ready: " + result, hasVirtualDisk); 72 73 waitForVolumeReadyNoCheckingOrEjecting(); 74 } 75 } 76 77 // Destroy the volume created before cleanUpVolume()78 public void cleanUpVolume() throws Exception { 79 mTest.getDevice().executeShellCommand("sm partition " + mDiskId + " public"); 80 mTest.getDevice().executeShellCommand("sm forget all"); 81 } 82 getAdoptionDisk()83 private String getAdoptionDisk() throws Exception { 84 // In the case where we run multiple test we cleanup the state of the device. This 85 // results in the execution of sm forget all which causes the MountService to "reset" 86 // all its knowledge about available drives. This can cause the adoptable drive to 87 // become temporarily unavailable. 88 int attempt = 0; 89 String disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable"); 90 while ((disks == null || disks.isEmpty()) && attempt++ < 15) { 91 Thread.sleep(1000); 92 disks = mTest.getDevice().executeShellCommand("sm list-disks adoptable"); 93 } 94 95 if (disks == null || disks.isEmpty()) { 96 throw new AssertionError( 97 "Devices that claim to support adoptable storage must have " 98 + "adoptable media inserted during CTS to verify correct behavior"); 99 } 100 return disks.split("\n")[0].trim(); 101 } 102 assertEmpty(String str)103 private static void assertEmpty(String str) { 104 if (str != null && str.trim().length() > 0) { 105 throw new AssertionError("Expected empty string but found " + str); 106 } 107 } 108 109 private static class LocalVolumeInfo { 110 public String volId; 111 public String state; 112 public String uuid; 113 LocalVolumeInfo(String line)114 LocalVolumeInfo(String line) { 115 final String[] split = line.split(" "); 116 volId = split[0]; 117 state = split[1]; 118 uuid = split[2]; 119 } 120 } 121 getAdoptionVolume()122 private LocalVolumeInfo getAdoptionVolume() throws Exception { 123 String[] lines = null; 124 int attempt = 0; 125 int mounted_count = 0; 126 while (attempt++ < 15) { 127 lines = mTest.getDevice().executeShellCommand("sm list-volumes private").split("\n"); 128 LogUtil.CLog.w("getAdoptionVolume(): " + Arrays.toString(lines)); 129 for (String line : lines) { 130 final LocalVolumeInfo info = new LocalVolumeInfo(line.trim()); 131 if (!"private".equals(info.volId)) { 132 if ("mounted".equals(info.state)) { 133 // make sure the storage is mounted and stable for a while 134 mounted_count++; 135 attempt--; 136 if (mounted_count >= 3) { 137 return waitForVolumeReady(info); 138 } 139 } else { 140 mounted_count = 0; 141 } 142 } 143 } 144 Thread.sleep(1000); 145 } 146 throw new AssertionError("Expected private volume; found " + Arrays.toString(lines)); 147 } 148 waitForVolumeReady(LocalVolumeInfo vol)149 private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol) throws Exception { 150 int attempt = 0; 151 while (attempt++ < 15) { 152 if (mTest.getDevice() 153 .executeShellCommand("dumpsys package volumes") 154 .contains(vol.volId)) { 155 return vol; 156 } 157 Thread.sleep(1000); 158 } 159 throw new AssertionError("Volume not ready " + vol.volId); 160 } 161 162 /** Ensure no volume is in ejecting or checking state */ waitForVolumeReadyNoCheckingOrEjecting()163 private void waitForVolumeReadyNoCheckingOrEjecting() throws Exception { 164 int attempt = 0; 165 boolean noCheckingEjecting = false; 166 String result = ""; 167 while (!noCheckingEjecting && attempt++ < 600) { 168 result = mTest.getDevice().executeShellCommand("sm list-volumes"); 169 noCheckingEjecting = !result.contains("ejecting") && !result.contains("checking"); 170 RunUtil.getDefault().sleep(100); 171 } 172 assertTrue("Volumes are not ready: " + result, noCheckingEjecting); 173 } 174 } 175