1 /* 2 * Copyright (C) 2015 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.appsecurity.cts; 18 19 import static android.appsecurity.cts.SplitTests.ABI_TO_APK; 20 import static android.appsecurity.cts.SplitTests.APK; 21 import static android.appsecurity.cts.SplitTests.APK_mdpi; 22 import static android.appsecurity.cts.SplitTests.APK_xxhdpi; 23 import static android.appsecurity.cts.SplitTests.CLASS; 24 import static android.appsecurity.cts.SplitTests.PKG; 25 26 import android.appsecurity.cts.SplitTests.BaseInstallMultiple; 27 import com.android.cts.migration.MigrationHelper; 28 import com.android.tradefed.build.IBuildInfo; 29 import com.android.tradefed.device.CollectingOutputReceiver; 30 import com.android.tradefed.device.DeviceNotAvailableException; 31 import com.android.tradefed.testtype.DeviceTestCase; 32 import com.android.tradefed.testtype.IAbi; 33 import com.android.tradefed.testtype.IAbiReceiver; 34 import com.android.tradefed.testtype.IBuildReceiver; 35 36 import java.util.Arrays; 37 import java.util.concurrent.TimeUnit; 38 39 /** 40 * Set of tests that verify behavior of adopted storage media, if supported. 41 */ 42 public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver { 43 private IAbi mAbi; 44 private IBuildInfo mCtsBuild; 45 46 @Override setAbi(IAbi abi)47 public void setAbi(IAbi abi) { 48 mAbi = abi; 49 } 50 51 @Override setBuild(IBuildInfo buildInfo)52 public void setBuild(IBuildInfo buildInfo) { 53 mCtsBuild = buildInfo; 54 } 55 56 @Override setUp()57 protected void setUp() throws Exception { 58 super.setUp(); 59 60 assertNotNull(mAbi); 61 assertNotNull(mCtsBuild); 62 63 getDevice().uninstallPackage(PKG); 64 } 65 66 @Override tearDown()67 protected void tearDown() throws Exception { 68 super.tearDown(); 69 70 getDevice().uninstallPackage(PKG); 71 } 72 testApps()73 public void testApps() throws Exception { 74 if (!hasAdoptable()) return; 75 final String diskId = getAdoptionDisk(); 76 try { 77 final String abi = mAbi.getName(); 78 final String apk = ABI_TO_APK.get(abi); 79 assertNotNull("Failed to find APK for ABI " + abi, apk); 80 81 // Install simple app on internal 82 new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run(); 83 runDeviceTests(PKG, CLASS, "testDataInternal"); 84 runDeviceTests(PKG, CLASS, "testDataWrite"); 85 runDeviceTests(PKG, CLASS, "testDataRead"); 86 runDeviceTests(PKG, CLASS, "testNative"); 87 88 // Adopt that disk! 89 assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private")); 90 final LocalVolumeInfo vol = getAdoptionVolume(); 91 92 // Move app and verify 93 assertSuccess(getDevice().executeShellCommand( 94 "pm move-package " + PKG + " " + vol.uuid)); 95 runDeviceTests(PKG, CLASS, "testDataNotInternal"); 96 runDeviceTests(PKG, CLASS, "testDataRead"); 97 runDeviceTests(PKG, CLASS, "testNative"); 98 99 // Unmount, remount and verify 100 getDevice().executeShellCommand("sm unmount " + vol.volId); 101 getDevice().executeShellCommand("sm mount " + vol.volId); 102 runDeviceTests(PKG, CLASS, "testDataNotInternal"); 103 runDeviceTests(PKG, CLASS, "testDataRead"); 104 runDeviceTests(PKG, CLASS, "testNative"); 105 106 // Move app back and verify 107 assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal")); 108 runDeviceTests(PKG, CLASS, "testDataInternal"); 109 runDeviceTests(PKG, CLASS, "testDataRead"); 110 runDeviceTests(PKG, CLASS, "testNative"); 111 112 // Un-adopt volume and app should still be fine 113 getDevice().executeShellCommand("sm partition " + diskId + " public"); 114 runDeviceTests(PKG, CLASS, "testDataInternal"); 115 runDeviceTests(PKG, CLASS, "testDataRead"); 116 runDeviceTests(PKG, CLASS, "testNative"); 117 118 } finally { 119 cleanUp(diskId); 120 } 121 } 122 testPrimaryStorage()123 public void testPrimaryStorage() throws Exception { 124 if (!hasAdoptable()) return; 125 final String diskId = getAdoptionDisk(); 126 try { 127 final String originalVol = getDevice() 128 .executeShellCommand("sm get-primary-storage-uuid").trim(); 129 130 if ("null".equals(originalVol)) { 131 verifyPrimaryInternal(diskId); 132 } else if ("primary_physical".equals(originalVol)) { 133 verifyPrimaryPhysical(diskId); 134 } 135 } finally { 136 cleanUp(diskId); 137 } 138 } 139 verifyPrimaryInternal(String diskId)140 private void verifyPrimaryInternal(String diskId) throws Exception { 141 // Write some data to shared storage 142 new InstallMultiple().addApk(APK).run(); 143 runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume"); 144 runDeviceTests(PKG, CLASS, "testPrimaryInternal"); 145 runDeviceTests(PKG, CLASS, "testPrimaryDataWrite"); 146 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 147 148 // Adopt that disk! 149 assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private")); 150 final LocalVolumeInfo vol = getAdoptionVolume(); 151 152 // Move storage there and verify that data went along for ride 153 CollectingOutputReceiver out = new CollectingOutputReceiver(); 154 getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid, out, 2, 155 TimeUnit.HOURS, 1); 156 assertSuccess(out.getOutput()); 157 runDeviceTests(PKG, CLASS, "testPrimaryAdopted"); 158 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 159 160 // Unmount and verify 161 getDevice().executeShellCommand("sm unmount " + vol.volId); 162 runDeviceTests(PKG, CLASS, "testPrimaryUnmounted"); 163 getDevice().executeShellCommand("sm mount " + vol.volId); 164 runDeviceTests(PKG, CLASS, "testPrimaryAdopted"); 165 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 166 167 // Move app and verify backing storage volume is same 168 assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid)); 169 runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume"); 170 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 171 172 // And move back to internal 173 out = new CollectingOutputReceiver(); 174 getDevice().executeShellCommand("pm move-primary-storage internal", out, 2, 175 TimeUnit.HOURS, 1); 176 assertSuccess(out.getOutput()); 177 178 runDeviceTests(PKG, CLASS, "testPrimaryInternal"); 179 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 180 181 assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal")); 182 runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume"); 183 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 184 } 185 verifyPrimaryPhysical(String diskId)186 private void verifyPrimaryPhysical(String diskId) throws Exception { 187 // Write some data to shared storage 188 new InstallMultiple().addApk(APK).run(); 189 runDeviceTests(PKG, CLASS, "testPrimaryPhysical"); 190 runDeviceTests(PKG, CLASS, "testPrimaryDataWrite"); 191 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 192 193 // Adopt that disk! 194 assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private")); 195 final LocalVolumeInfo vol = getAdoptionVolume(); 196 197 // Move primary storage there, but since we just nuked primary physical 198 // the storage device will be empty 199 assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid)); 200 runDeviceTests(PKG, CLASS, "testPrimaryAdopted"); 201 runDeviceTests(PKG, CLASS, "testPrimaryDataWrite"); 202 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 203 204 // Unmount and verify 205 getDevice().executeShellCommand("sm unmount " + vol.volId); 206 runDeviceTests(PKG, CLASS, "testPrimaryUnmounted"); 207 getDevice().executeShellCommand("sm mount " + vol.volId); 208 runDeviceTests(PKG, CLASS, "testPrimaryAdopted"); 209 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 210 211 // And move to internal 212 assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal")); 213 runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume"); 214 runDeviceTests(PKG, CLASS, "testPrimaryInternal"); 215 runDeviceTests(PKG, CLASS, "testPrimaryDataRead"); 216 } 217 218 /** 219 * Verify that we can install both new and inherited packages directly on 220 * adopted volumes. 221 */ testPackageInstaller()222 public void testPackageInstaller() throws Exception { 223 if (!hasAdoptable()) return; 224 final String diskId = getAdoptionDisk(); 225 try { 226 assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private")); 227 final LocalVolumeInfo vol = getAdoptionVolume(); 228 229 // Install directly onto adopted volume 230 new InstallMultiple().locationAuto().forceUuid(vol.uuid) 231 .addApk(APK).addApk(APK_mdpi).run(); 232 runDeviceTests(PKG, CLASS, "testDataNotInternal"); 233 runDeviceTests(PKG, CLASS, "testDensityBest1"); 234 235 // Now splice in an additional split which offers better resources 236 new InstallMultiple().locationAuto().inheritFrom(PKG) 237 .addApk(APK_xxhdpi).run(); 238 runDeviceTests(PKG, CLASS, "testDataNotInternal"); 239 runDeviceTests(PKG, CLASS, "testDensityBest2"); 240 241 } finally { 242 cleanUp(diskId); 243 } 244 } 245 246 /** 247 * Verify behavior when changes occur while adopted device is ejected and 248 * returned at a later time. 249 */ testEjected()250 public void testEjected() throws Exception { 251 if (!hasAdoptable()) return; 252 final String diskId = getAdoptionDisk(); 253 try { 254 assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private")); 255 final LocalVolumeInfo vol = getAdoptionVolume(); 256 257 // Install directly onto adopted volume, and write data there 258 new InstallMultiple().locationAuto().forceUuid(vol.uuid).addApk(APK).run(); 259 runDeviceTests(PKG, CLASS, "testDataNotInternal"); 260 runDeviceTests(PKG, CLASS, "testDataWrite"); 261 runDeviceTests(PKG, CLASS, "testDataRead"); 262 263 // Now unmount and uninstall; leaving stale package on adopted volume 264 getDevice().executeShellCommand("sm unmount " + vol.volId); 265 getDevice().uninstallPackage(PKG); 266 267 // Install second copy on internal, but don't write anything 268 new InstallMultiple().locationInternalOnly().addApk(APK).run(); 269 runDeviceTests(PKG, CLASS, "testDataInternal"); 270 271 // Kick through a remount cycle, which should purge the adopted app 272 getDevice().executeShellCommand("sm mount " + vol.volId); 273 runDeviceTests(PKG, CLASS, "testDataInternal"); 274 try { 275 runDeviceTests(PKG, CLASS, "testDataRead"); 276 fail("Unexpected data from adopted volume picked up"); 277 } catch (AssertionError expected) { 278 } 279 getDevice().executeShellCommand("sm unmount " + vol.volId); 280 281 // Uninstall the internal copy and remount; we should have no record of app 282 getDevice().uninstallPackage(PKG); 283 getDevice().executeShellCommand("sm mount " + vol.volId); 284 285 assertEmpty(getDevice().executeShellCommand("pm list packages " + PKG)); 286 } finally { 287 cleanUp(diskId); 288 } 289 } 290 hasAdoptable()291 private boolean hasAdoptable() throws Exception { 292 return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim()); 293 } 294 getAdoptionDisk()295 private String getAdoptionDisk() throws Exception { 296 final String disks = getDevice().executeShellCommand("sm list-disks adoptable"); 297 if (disks == null || disks.length() == 0) { 298 throw new AssertionError("Devices that claim to support adoptable storage must have " 299 + "adoptable media inserted during CTS to verify correct behavior"); 300 } 301 return disks.split("\n")[0].trim(); 302 } 303 getAdoptionVolume()304 private LocalVolumeInfo getAdoptionVolume() throws Exception { 305 String[] lines = null; 306 int attempt = 0; 307 while (attempt++ < 15) { 308 lines = getDevice().executeShellCommand("sm list-volumes private").split("\n"); 309 for (String line : lines) { 310 final LocalVolumeInfo info = new LocalVolumeInfo(line.trim()); 311 if (!"private".equals(info.volId) && "mounted".equals(info.state)) { 312 return info; 313 } 314 } 315 Thread.sleep(1000); 316 } 317 throw new AssertionError("Expected private volume; found " + Arrays.toString(lines)); 318 } 319 cleanUp(String diskId)320 private void cleanUp(String diskId) throws Exception { 321 getDevice().executeShellCommand("sm partition " + diskId + " public"); 322 getDevice().executeShellCommand("sm forget all"); 323 } 324 runDeviceTests(String packageName, String testClassName, String testMethodName)325 private void runDeviceTests(String packageName, String testClassName, String testMethodName) 326 throws DeviceNotAvailableException { 327 Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName); 328 } 329 assertSuccess(String str)330 private static void assertSuccess(String str) { 331 if (str == null || !str.startsWith("Success")) { 332 throw new AssertionError("Expected success string but found " + str); 333 } 334 } 335 assertEmpty(String str)336 private static void assertEmpty(String str) { 337 if (str != null && str.trim().length() > 0) { 338 throw new AssertionError("Expected empty string but found " + str); 339 } 340 } 341 342 private static class LocalVolumeInfo { 343 public String volId; 344 public String state; 345 public String uuid; 346 LocalVolumeInfo(String line)347 public LocalVolumeInfo(String line) { 348 final String[] split = line.split(" "); 349 volId = split[0]; 350 state = split[1]; 351 uuid = split[2]; 352 } 353 } 354 355 private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { InstallMultiple()356 public InstallMultiple() { 357 super(getDevice(), mCtsBuild, mAbi); 358 } 359 } 360 } 361