/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.sdklib; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.resources.Density; import com.android.resources.Keyboard; import com.android.resources.KeyboardState; import com.android.resources.Navigation; import com.android.resources.NavigationState; import com.android.resources.ScreenOrientation; import com.android.resources.ScreenRatio; import com.android.resources.ScreenSize; import com.android.resources.TouchScreen; import com.android.sdklib.ISystemImage.LocationType; import com.android.sdklib.devices.ButtonType; import com.android.sdklib.devices.Device; import com.android.sdklib.devices.Device.Builder; import com.android.sdklib.devices.DeviceWriter; import com.android.sdklib.devices.Hardware; import com.android.sdklib.devices.Multitouch; import com.android.sdklib.devices.PowerType; import com.android.sdklib.devices.Screen; import com.android.sdklib.devices.ScreenType; import com.android.sdklib.devices.Software; import com.android.sdklib.devices.State; import com.android.sdklib.devices.Storage; import com.android.sdklib.devices.Storage.Unit; import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.io.FileOp; import com.android.sdklib.mock.MockLog; import com.android.sdklib.repository.FullRevision; import com.android.sdklib.repository.PkgProps; import com.android.sdklib.repository.SdkRepoConstants; import com.android.sdklib.repository.local.LocalPlatformPkgInfo; import com.android.sdklib.repository.local.LocalSysImgPkgInfo; import com.android.utils.ILogger; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Base Test case that allocates a temporary SDK, a temporary AVD base folder with an SdkManager and * an AvdManager that points to them. <p/> Also overrides the {@link AndroidLocation} to point to * temp one. */ public abstract class SdkManagerTestCase extends AndroidLocationTestCase { protected static final String TARGET_DIR_NAME_0 = "v0_0"; private File mFakeSdk; private MockLog mLog; private SdkManager mSdkManager; private AvdManager mAvdManager; private int mRepoXsdLevel; /** * Returns the {@link MockLog} for this test case. */ public MockLog getLog() { return mLog; } /** * Returns the {@link SdkManager} for this test case. */ public SdkManager getSdkManager() { return mSdkManager; } /** * Returns the {@link AvdManager} for this test case. */ public AvdManager getAvdManager() { return mAvdManager; } /** * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to * an initially-empty AVD directory. */ public void setUp(int repoXsdLevel) throws Exception { super.setUp(); mRepoXsdLevel = repoXsdLevel; mLog = new MockLog(); makeFakeSdk(); createSdkAvdManagers(); } /** * Recreate the SDK and AVD Managers from scratch even if they already existed. Useful for tests * that want to reset their state without recreating the android-home or the fake SDK. The SDK * will be reparsed. */ protected void createSdkAvdManagers() throws AndroidLocationException { mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog); assertNotNull("SdkManager location was invalid", mSdkManager); // Note: it's safe to use the default AvdManager implementation since makeFakeAndroidHome // above overrides the ANDROID_HOME folder to use a temp folder; consequently all // the AVDs created here will be located in this temp folder and will not alter // or pollute the default user's AVD folder. mAvdManager = new AvdManager(mSdkManager.getLocalSdk(), mLog) { @Override protected boolean createSdCard( String toolLocation, String size, String location, ILogger log) { if (new File(toolLocation).exists()) { log.info("[EXEC] %1$s %2$s %3$s\n", toolLocation, size, location); return true; } else { log.error(null, "Failed to create the SD card.\n"); return false; } } }; } /** * Sets up a {@link MockLog}, a fake SDK in a temporary directory and an AVD Manager pointing to * an initially-empty AVD directory. */ @Override public void setUp() throws Exception { setUp(SdkRepoConstants.NS_LATEST_VERSION); } /** * Removes the temporary SDK and AVD directories. */ @Override public void tearDown() throws Exception { tearDownSdk(); super.tearDown(); } /** * Build enough of a skeleton SDK to make the tests pass. <p/> Ideally this wouldn't touch the * file system but the current structure of the SdkManager and AvdManager makes this * impossible. */ private void makeFakeSdk() throws IOException { // First we create a temp file to "reserve" the temp directory name we want to use. mFakeSdk = File.createTempFile( "sdk_" + this.getClass().getSimpleName() + '_' + this.getName(), null); // Then erase the file and make the directory mFakeSdk.delete(); mFakeSdk.mkdirs(); File addonsDir = new File(mFakeSdk, SdkConstants.FD_ADDONS); addonsDir.mkdir(); File toolsDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_FOLDER); toolsDir.mkdir(); createSourceProps(toolsDir, PkgProps.PKG_REVISION, "1.0.1"); new File(toolsDir, SdkConstants.androidCmdName()).createNewFile(); new File(toolsDir, SdkConstants.FN_EMULATOR).createNewFile(); new File(toolsDir, SdkConstants.mkSdCardCmdName()).createNewFile(); makePlatformTools(new File(mFakeSdk, SdkConstants.FD_PLATFORM_TOOLS)); if (mRepoXsdLevel >= 8) { makeBuildTools(mFakeSdk); } File toolsLibEmuDir = new File(mFakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator"); toolsLibEmuDir.mkdirs(); new File(toolsLibEmuDir, "snapshots.img").createNewFile(); File platformsDir = new File(mFakeSdk, SdkConstants.FD_PLATFORMS); // Creating a fake target here on down File targetDir = makeFakeTargetInternal(platformsDir); makeFakeLegacySysImg(targetDir, SdkConstants.ABI_ARMEABI); makeFakeSkin(targetDir, "HVGA"); makeFakeSourceInternal(mFakeSdk); } private void tearDownSdk() { deleteDir(mFakeSdk); } /** * Creates the system image folder and places a fake userdata.img in it. * * @param systemImage A system image with a valid location. */ protected void makeSystemImageFolder(ISystemImage systemImage, String deviceId) throws Exception { File sysImgDir = systemImage.getLocation(); String vendor = systemImage.getAddonVendor() == null ? null : systemImage.getAddonVendor().getId(); if (systemImage.getLocationType() == LocationType.IN_LEGACY_FOLDER) { // legacy mode. Path should look like SDK/platforms/platform-N/userdata.img makeFakeLegacySysImg(sysImgDir.getParentFile(), systemImage.getAbiType()); } else if (systemImage.getLocationType() == LocationType.IN_IMAGES_SUBFOLDER) { // not-so-legacy mode. // Path should look like SDK/platforms/platform-N/images/userdata.img makeFakeSysImgInternal( sysImgDir, systemImage.getTag().getId(), systemImage.getAbiType(), deviceId, vendor); } else if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) { // system-image folder mode. // Path should like SDK/system-images/platform-N/tag/abi/userdata.img+source.properties makeFakeSysImgInternal( sysImgDir, systemImage.getTag().getId(), systemImage.getAbiType(), deviceId, vendor); } } /** * Creates the system image folder and places a fake userdata.img in it. This must be called * after {@link #setUp()} so that it can use the temp fake SDK folder, and consequently you do * not need to specify the SDK root. * * @param targetDir The targetDir segment of the sys-image folder. Use {@link * #TARGET_DIR_NAME_0} to match the default single platform. * @param tagId An optional tag id. Use null for legacy no-tag system images. * @param abiType The abi for the system image. * @return The directory of the system-image/tag/abi created. * @throws IOException if the file fails to be created. */ @NonNull protected File makeSystemImageFolder( @NonNull String targetDir, @Nullable String tagId, @NonNull String abiType) throws Exception { File sysImgDir = new File(mFakeSdk, SdkConstants.FD_SYSTEM_IMAGES); sysImgDir = new File(sysImgDir, targetDir); if (tagId != null) { sysImgDir = new File(sysImgDir, tagId); } sysImgDir = new File(sysImgDir, abiType); makeFakeSysImgInternal(sysImgDir, tagId, abiType, null, null); return sysImgDir; } //---- private void createTextFile(File dir, String filepath, String... lines) throws IOException { File file = new File(dir, filepath); File parent = file.getParentFile(); if (!parent.isDirectory()) { parent.mkdirs(); } if (!file.isFile()) { assertTrue(file.createNewFile()); } if (lines != null && lines.length > 0) { FileWriter out = new FileWriter(file); for (String line : lines) { out.write(line); } out.close(); } } /** * Utility used by {@link #makeFakeSdk()} to create a fake target with API 0, rev 0. */ private File makeFakeTargetInternal(File platformsDir) throws IOException { File targetDir = new File(platformsDir, TARGET_DIR_NAME_0); targetDir.mkdirs(); new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile(); new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile(); createSourceProps(targetDir, PkgProps.PKG_REVISION, "1", PkgProps.PLATFORM_VERSION, "0.0", PkgProps.VERSION_API_LEVEL, "0", PkgProps.LAYOUTLIB_API, "5", PkgProps.LAYOUTLIB_REV, "2"); createFileProps(SdkConstants.FN_BUILD_PROP, targetDir, LocalPlatformPkgInfo.PROP_VERSION_RELEASE, "0.0", LocalPlatformPkgInfo.PROP_VERSION_SDK, "0", LocalPlatformPkgInfo.PROP_VERSION_CODENAME, "REL"); return targetDir; } /** * Utility to create a fake *legacy* sys image in a platform folder. Legacy system images follow * that path pattern: $SDK/platforms/platform-N/images/userdata.img * * They have no source.properties file in that directory. */ private void makeFakeLegacySysImg( @NonNull File platformDir, @NonNull String abiType) throws IOException { File imagesDir = new File(platformDir, "images"); imagesDir.mkdirs(); new File(imagesDir, "userdata.img").createNewFile(); } /** * Utility to create a fake sys image in the system-images folder. * * "modern" (as in "not legacy") system-images follow that path pattern: * $SDK/system-images/platform-N/abi/source.properties $SDK/system-images/platform-N/abi/userdata.img * or $SDK/system-images/platform-N/tag/abi/source.properties $SDK/system-images/platform-N/tag/abi/userdata.img * * The tag id is optional and was only introduced in API 20 / Tools 22.6. The platform-N and the * tag folder names are irrelevant as the info from source.properties matters most. */ private void makeFakeSysImgInternal( @NonNull File sysImgDir, @Nullable String tagId, @NonNull String abiType, @Nullable String deviceId, @Nullable String deviceMfg) throws Exception { sysImgDir.mkdirs(); new File(sysImgDir, "userdata.img").createNewFile(); if (tagId == null) { createSourceProps(sysImgDir, PkgProps.PKG_REVISION, "0", PkgProps.VERSION_API_LEVEL, "0", PkgProps.SYS_IMG_ABI, abiType); } else { String tagDisplay = LocalSysImgPkgInfo.tagIdToDisplay(tagId); createSourceProps(sysImgDir, PkgProps.PKG_REVISION, "0", PkgProps.VERSION_API_LEVEL, "0", PkgProps.SYS_IMG_TAG_ID, tagId, PkgProps.SYS_IMG_TAG_DISPLAY, tagDisplay, PkgProps.SYS_IMG_ABI, abiType, PkgProps.PKG_LIST_DISPLAY, "Sys-Img v0 for (" + tagDisplay + ", " + abiType + ")"); // create a devices.xml file List<Device> devices = new ArrayList<Device>(); Builder b = new Device.Builder(); b.setName("Mock " + tagDisplay + " Device Name"); b.setId(deviceId == null ? "MockDevice-" + tagId : deviceId); b.setManufacturer(deviceMfg == null ? "Mock " + tagDisplay + " OEM" : deviceMfg); Software sw = new Software(); sw.setGlVersion("4.2"); sw.setLiveWallpaperSupport(false); sw.setMaxSdkLevel(42); sw.setMinSdkLevel(1); sw.setStatusBar(true); Screen sc = new Screen(); sc.setDiagonalLength(7); sc.setMechanism(TouchScreen.FINGER); sc.setMultitouch(Multitouch.JAZZ_HANDS); sc.setPixelDensity(Density.HIGH); sc.setRatio(ScreenRatio.NOTLONG); sc.setScreenType(ScreenType.CAPACITIVE); sc.setSize(ScreenSize.LARGE); sc.setXDimension(5); sc.setXdpi(100); sc.setYDimension(4); sc.setYdpi(100); Hardware hw = new Hardware(); hw.setButtonType(ButtonType.SOFT); hw.setChargeType(PowerType.BATTERY); hw.setCpu(abiType); hw.setGpu("pixelpushing"); hw.setHasMic(true); hw.setKeyboard(Keyboard.QWERTY); hw.setNav(Navigation.NONAV); hw.setRam(new Storage(512, Unit.MiB)); hw.setScreen(sc); State st = new State(); st.setName("portrait"); st.setDescription("Portrait"); st.setDefaultState(true); st.setOrientation(ScreenOrientation.PORTRAIT); st.setKeyState(KeyboardState.SOFT); st.setNavState(NavigationState.HIDDEN); st.setHardware(hw); b.addSoftware(sw); b.addState(st); devices.add(b.build()); File f = new File(sysImgDir, "devices.xml"); FileOutputStream fos = new FileOutputStream(f); DeviceWriter.writeToXml(fos, devices); fos.close(); } } /** * Utility to make a fake skin for the given target */ protected void makeFakeSkin(File targetDir, String skinName) throws IOException { File skinFolder = FileOp.append(targetDir, "skins", skinName); skinFolder.mkdirs(); // To be detected properly, the skin folder should have a "layout" file. // Its content is however not parsed. FileWriter out = new FileWriter(new File(skinFolder, "layout")); out.write("parts {\n}\n"); out.close(); } /** * Utility to create a fake source with a few files in the given sdk folder. */ private void makeFakeSourceInternal(File sdkDir) throws IOException { File sourcesDir = FileOp.append(sdkDir, SdkConstants.FD_PKG_SOURCES, "android-0"); sourcesDir.mkdirs(); createSourceProps(sourcesDir, PkgProps.VERSION_API_LEVEL, "0"); File dir1 = FileOp.append(sourcesDir, "src", "com", "android"); dir1.mkdirs(); FileOp.append(dir1, "File1.java").createNewFile(); FileOp.append(dir1, "File2.java").createNewFile(); FileOp.append(sourcesDir, "res", "values").mkdirs(); FileOp.append(sourcesDir, "res", "values", "styles.xml").createNewFile(); } private void makePlatformTools(File platformToolsDir) throws IOException { platformToolsDir.mkdir(); createSourceProps(platformToolsDir, PkgProps.PKG_REVISION, "17.1.2"); // platform-tools revision >= 17 requires only an adb file to be valid. new File(platformToolsDir, SdkConstants.FN_ADB).createNewFile(); } private void makeBuildTools(File sdkDir) throws IOException { for (String revision : new String[]{"3.0.0", "3.0.1", "18.3.4 rc5"}) { createFakeBuildTools(sdkDir, "ANY", revision); } } /** * Adds a new fake build tools to the SDK In the given SDK/build-tools folder. * * @param sdkDir The SDK top folder. Must already exist. * @param os The OS. One of HostOs#toString() or "ANY". * @param revision The "x.y.z rc r" revision number from {@link FullRevision#toShortString()}. */ protected void createFakeBuildTools(File sdkDir, String os, String revision) throws IOException { File buildToolsTopDir = new File(sdkDir, SdkConstants.FD_BUILD_TOOLS); buildToolsTopDir.mkdir(); File buildToolsDir = new File(buildToolsTopDir, revision); createSourceProps(buildToolsDir, PkgProps.PKG_REVISION, revision, "Archive.Os", os); FullRevision fullRevision = FullRevision.parseRevision(revision); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.AAPT, SdkConstants.FN_AAPT); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.AIDL, SdkConstants.FN_AIDL); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.DX, SdkConstants.FN_DX); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.DX_JAR, SdkConstants.FD_LIB + File.separator + SdkConstants.FN_DX_JAR); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS + File.separator + "placeholder.txt"); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.ANDROID_RS_CLANG, SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator + "placeholder.txt"); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.LD_ARM, SdkConstants.FN_LD_ARM); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.LD_MIPS, SdkConstants.FN_LD_MIPS); createFakeBuildToolsFile( buildToolsDir, fullRevision, BuildToolInfo.PathId.LD_X86, SdkConstants.FN_LD_X86); } private void createFakeBuildToolsFile(@NonNull File dir, @NonNull FullRevision buildToolsRevision, @NonNull BuildToolInfo.PathId pathId, @NonNull String filepath) throws IOException { if (pathId.isPresentIn(buildToolsRevision)) { createTextFile(dir, filepath); } } protected void createSourceProps(File parentDir, String... paramValuePairs) throws IOException { createFileProps(SdkConstants.FN_SOURCE_PROP, parentDir, paramValuePairs); } protected void createFileProps(String fileName, File parentDir, String... paramValuePairs) throws IOException { File sourceProp = new File(parentDir, fileName); parentDir = sourceProp.getParentFile(); if (!parentDir.isDirectory()) { assertTrue(parentDir.mkdirs()); } if (!sourceProp.isFile()) { assertTrue(sourceProp.createNewFile()); } FileWriter out = new FileWriter(sourceProp); int n = paramValuePairs.length; assertTrue("paramValuePairs must have an even length, format [param=value]+", n % 2 == 0); for (int i = 0; i < n; i += 2) { out.write(paramValuePairs[i] + '=' + paramValuePairs[i + 1] + '\n'); } out.close(); } /** * Recursive delete directory. Mostly for fake SDKs. * * @param root directory to delete */ protected void deleteDir(File root) { if (root.exists()) { for (File file : root.listFiles()) { if (file.isDirectory()) { deleteDir(file); } else { file.delete(); } } root.delete(); } } }