private void uninstall( @NonNull File apkFile, @Nullable String packageName, @NonNull String deviceName) throws DeviceException { if (packageName != null) { logger.verbose("DeviceConnector '%s': uninstalling %s", deviceName, packageName); device.uninstallPackage(packageName, timeoutInMs, logger); } else { logger.verbose( "DeviceConnector '%s': unable to uninstall %s: unable to get package name", deviceName, apkFile); } }
/** * Check if any error occurred during initialization. If it did, display an error message. * * @return True if an error occurred, false if we should continue. */ public boolean checkIfInitFailed() { if (mAvdManagerInitError != null) { String example; if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) { example = "%USERPROFILE%"; // $NON-NLS-1$ } else { example = "~"; // $NON-NLS-1$ } String error = String.format( "The AVD manager normally uses the user's profile directory to store " + "AVD files. However it failed to find the default profile directory. " + "\n" + "To fix this, please set the environment variable ANDROID_SDK_HOME to " + "a valid path such as \"%s\".", example); // We may not have any UI. Only display a dialog if there's a window shell available. if (mWindowShell != null && !mWindowShell.isDisposed()) { MessageDialog.openError(mWindowShell, "Android Virtual Devices Manager", error); } else { mSdkLog.error(null /* Throwable */, "%s", error); // $NON-NLS-1$ } return true; } return false; }
/** * Reloads the AVDs. * * <p>This does not notify the listeners. */ public void reloadAvds() { // reload AVDs if (mAvdManager != null) { try { mAvdManager.reloadAvds(mSdkLog); } catch (AndroidLocationException e) { mSdkLog.error(e, null); } } }
private List<LoadedManifestInfo> loadLibraries( SelectorResolver selectors, MergingReport.Builder mergingReportBuilder) throws MergeFailureException { ImmutableList.Builder<LoadedManifestInfo> loadedLibraryDocuments = ImmutableList.builder(); for (Pair<String, File> libraryFile : mLibraryFiles) { mLogger.info("Loading library manifest " + libraryFile.getSecond().getPath()); ManifestInfo manifestInfo = new ManifestInfo( libraryFile.getFirst(), libraryFile.getSecond(), XmlDocument.Type.LIBRARY, Optional.<String>absent()); XmlDocument libraryDocument; try { libraryDocument = XmlLoader.load( selectors, mSystemPropertyResolver, manifestInfo.mName, manifestInfo.mLocation, XmlDocument.Type.LIBRARY, Optional.<String>absent() /* mainManifestPackageName */); } catch (Exception e) { throw new MergeFailureException(e); } // extract the package name... String libraryPackage = libraryDocument.getRootNode().getXml().getAttribute("package"); // save it in the selector instance. if (!Strings.isNullOrEmpty(libraryPackage)) { selectors.addSelector(libraryPackage, libraryFile.getFirst()); } // perform placeholder substitution, this is useful when the library is using // a placeholder in a key element, we however do not need to record these // substitutions so feed it with a fake merging report. MergingReport.Builder builder = new MergingReport.Builder(mergingReportBuilder.getLogger()); builder.getActionRecorder().recordDefaultNodeAction(libraryDocument.getRootNode()); performPlaceHolderSubstitution(manifestInfo, libraryDocument, builder); if (builder.hasErrors()) { // we log the errors but continue, in case the error is of no consequence // to the application consuming the library. builder.build().log(mLogger); } loadedLibraryDocuments.add( new LoadedManifestInfo( manifestInfo, Optional.fromNullable(libraryDocument.getPackageName()), libraryDocument)); } return loadedLibraryDocuments.build(); }
/** * Lists remote packages available for install using {@link UpdaterData#updateOrInstallAll_NoGUI}. * * @param includeAll True to list and install all packages, including obsolete ones. * @param extendedOutput True to display more details on each package. */ public void listRemotePackages_NoGUI(boolean includeAll, boolean extendedOutput) { List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll); mSdkLog.info("Packages available for installation or update: %1$d\n", archives.size()); int index = 1; for (ArchiveInfo ai : archives) { Archive a = ai.getNewArchive(); if (a != null) { Package p = a.getParentPackage(); if (p != null) { if (extendedOutput) { mSdkLog.info("----------\n"); mSdkLog.info("id: %1$d or \"%2$s\"\n", index, p.installId()); mSdkLog.info( " Type: %1$s\n", p.getClass() .getSimpleName() .replaceAll("Package", "")); // $NON-NLS-1$ //$NON-NLS-2$ String desc = LineUtil.reformatLine(" Desc: %s\n", p.getLongDescription()); mSdkLog.info("%s", desc); // $NON-NLS-1$ } else { mSdkLog.info("%1$ 4d- %2$s\n", index, p.getShortDescription()); } index++; } } } }
/** * Creates the merging report file. * * @param mergingReport the merging activities report to serialize. */ private void writeReport(MergingReport mergingReport) { FileWriter fileWriter = null; try { if (!mReportFile.get().getParentFile().exists() && !mReportFile.get().getParentFile().mkdirs()) { mLogger.warning( String.format( "Cannot create %1$s manifest merger report file," + "build will continue but merging activities " + "will not be documented", mReportFile.get().getAbsolutePath())); } else { fileWriter = new FileWriter(mReportFile.get()); mergingReport.getActions().log(fileWriter); } } catch (IOException e) { mLogger.warning( String.format( "Error '%1$s' while writing the merger report file, " + "build can continue but merging activities " + "will not be documented ", e.getMessage())); } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { mLogger.warning( String.format( "Error '%1$s' while closing the merger report file, " + "build can continue but merging activities " + "will not be documented ", e.getMessage())); } } } }
protected void notifyToolsNeedsToBeRestarted(int flags) { String msg = null; if ((flags & TOOLS_MSG_UPDATED_FROM_ADT) == TOOLS_MSG_UPDATED_FROM_ADT) { msg = "The Android SDK and AVD Manager that you are currently using has been updated. " + "Please also run Eclipse > Help > Check for Updates to see if the Android " + "plug-in needs to be updated."; } else if ((flags & TOOLS_MSG_UPDATED_FROM_SDKMAN) == TOOLS_MSG_UPDATED_FROM_SDKMAN) { msg = "The Android SDK and AVD Manager that you are currently using has been updated. " + "It is recommended that you now close the manager window and re-open it. " + "If you use Eclipse, please run Help > Check for Updates to see if the Android " + "plug-in needs to be updated."; } else if ((flags & NO_TOOLS_MSG) == NO_TOOLS_MSG) { return; } mSdkLog.info("%s", msg); // $NON-NLS-1$ }
/** * Initializes the {@link SdkManager} and the {@link AvdManager}. Extracted so that we can * override this in unit tests. */ @VisibleForTesting(visibility = Visibility.PRIVATE) protected void initSdk() { setSdkManager(SdkManager.createManager(mOsSdkRoot, mSdkLog)); try { mAvdManager = null; mAvdManager = AvdManager.getInstance(mSdkManager, mSdkLog); } catch (AndroidLocationException e) { mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage()); // $NON-NLS-1$ // Note: we used to continue here, but the thing is that // mAvdManager==null so nothing is really going to work as // expected. Let's just display an error later in checkIfInitFailed() // and abort right there. This step is just too early in the SWT // setup process to display a message box yet. mAvdManagerInitError = e; } // notify listeners. broadcastOnSdkReload(); }
/** * Tries to update all the *existing* local packages. This version is intended to run without a * GUI and only outputs to the current {@link ILogger}. * * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()} or * package indexes to limit the packages we can update or install. A null or empty list means * to update everything possible. * @param includeAll True to list and install all packages, including obsolete ones. * @param dryMode True to check what would be updated/installed but do not actually download or * install anything. * @return A list of archives that have been installed. Can be null if nothing was done. */ public List<Archive> updateOrInstallAll_NoGUI( Collection<String> pkgFilter, boolean includeAll, boolean dryMode) { List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll); // Filter the selected archives to only keep the ones matching the filter if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) { // Map filter types to an SdkRepository Package type, // e.g. create a map "platform" => PlatformPackage.class HashMap<String, Class<? extends Package>> pkgMap = new HashMap<String, Class<? extends Package>>(); mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES); mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES); // Prepare a map install-id => package instance HashMap<String, Package> installIdMap = new HashMap<String, Package>(); for (ArchiveInfo ai : archives) { Archive a = ai.getNewArchive(); if (a != null) { Package p = a.getParentPackage(); if (p != null) { String id = p.installId(); if (id != null && id.length() > 0 && !installIdMap.containsKey(id)) { installIdMap.put(id, p); } } } } // Now intersect this with the pkgFilter requested by the user, in order to // only keep the classes that the user wants to install. // We also create a set with the package indices requested by the user // and a set of install-ids requested by the user. HashSet<Class<? extends Package>> userFilteredClasses = new HashSet<Class<? extends Package>>(); SparseIntArray userFilteredIndices = new SparseIntArray(); Set<String> userFilteredInstallIds = new HashSet<String>(); for (String type : pkgFilter) { if (installIdMap.containsKey(type)) { userFilteredInstallIds.add(type); } else if (type.replaceAll("[0-9]+", "").length() == 0) { // $NON-NLS-1$ //$NON-NLS-2$ // An all-digit number is a package index requested by the user. int index = Integer.parseInt(type); userFilteredIndices.put(index, index); } else if (pkgMap.containsKey(type)) { userFilteredClasses.add(pkgMap.get(type)); } else { // This should not happen unless there's a mismatch in the package map. mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type); } } // we don't need the maps anymore pkgMap = null; installIdMap = null; // Now filter the remote archives list to keep: // - any package which class matches userFilteredClasses // - any package index which matches userFilteredIndices // - any package install id which matches userFilteredInstallIds int index = 1; for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) { boolean keep = false; ArchiveInfo ai = it.next(); Archive a = ai.getNewArchive(); if (a != null) { Package p = a.getParentPackage(); if (p != null) { if (userFilteredInstallIds.contains(p.installId()) || userFilteredClasses.contains(p.getClass()) || userFilteredIndices.get(index) > 0) { keep = true; } index++; } } if (!keep) { it.remove(); } } if (archives.size() == 0) { mSdkLog.info( LineUtil.reflowLine( "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n")); return null; } } if (archives != null && archives.size() > 0) { if (dryMode) { mSdkLog.info("Packages selected for install:\n"); for (ArchiveInfo ai : archives) { Archive a = ai.getNewArchive(); if (a != null) { Package p = a.getParentPackage(); if (p != null) { mSdkLog.info("- %1$s\n", p.getShortDescription()); } } } mSdkLog.info("\nDry mode is on so nothing is actually being installed.\n"); } else { return installArchives(archives, NO_TOOLS_MSG); } } else { mSdkLog.info("There is nothing to install or update.\n"); } return null; }
/** * Perform high level ordering of files merging and delegates actual merging to {@link * XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)} * * @return the merging activity report. * @throws MergeFailureException if the merging cannot be completed (for instance, if xml files * cannot be loaded). */ private MergingReport merge() throws MergeFailureException { // initiate a new merging report MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger); SelectorResolver selectors = new SelectorResolver(); // load all the libraries xml files up front to have a list of all possible node:selector // values. List<LoadedManifestInfo> loadedLibraryDocuments = loadLibraries(selectors, mergingReportBuilder); // load the main manifest file to do some checking along the way. LoadedManifestInfo loadedMainManifestInfo = load( new ManifestInfo( mManifestFile.getName(), mManifestFile, XmlDocument.Type.MAIN, Optional.<String>absent() /* mainManifestPackageName */), selectors, mergingReportBuilder); // first do we have a package declaration in the main manifest ? Optional<XmlAttribute> mainPackageAttribute = loadedMainManifestInfo.getXmlDocument().getPackage(); if (!mainPackageAttribute.isPresent()) { mergingReportBuilder.addMessage( loadedMainManifestInfo.getXmlDocument().getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR, String.format( "Main AndroidManifest.xml at %1$s manifest:package attribute " + "is not declared", loadedMainManifestInfo.getXmlDocument().getSourceLocation().print(true))); return mergingReportBuilder.build(); } // check for placeholders presence. Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues; if (!mPlaceHolderValues.containsKey(APPLICATION_ID)) { finalPlaceHolderValues = ImmutableMap.<String, Object>builder() .putAll(mPlaceHolderValues) .put(PACKAGE_NAME, mainPackageAttribute.get().getValue()) .put(APPLICATION_ID, mainPackageAttribute.get().getValue()) .build(); } // perform system property injection performSystemPropertiesInjection(mergingReportBuilder, loadedMainManifestInfo.getXmlDocument()); // force the re-parsing of the xml as elements may have been added through system // property injection. loadedMainManifestInfo = new LoadedManifestInfo( loadedMainManifestInfo, loadedMainManifestInfo.getOriginalPackageName(), loadedMainManifestInfo.getXmlDocument().reparse()); // invariant : xmlDocumentOptional holds the higher priority document and we try to // merge in lower priority documents. Optional<XmlDocument> xmlDocumentOptional = Optional.absent(); for (File inputFile : mFlavorsAndBuildTypeFiles) { mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath()); LoadedManifestInfo overlayDocument = load( new ManifestInfo( null, inputFile, XmlDocument.Type.OVERLAY, Optional.of(mainPackageAttribute.get().getValue())), selectors, mergingReportBuilder); // check package declaration. Optional<XmlAttribute> packageAttribute = overlayDocument.getXmlDocument().getPackage(); // if both files declare a package name, it should be the same. if (loadedMainManifestInfo.getOriginalPackageName().isPresent() && packageAttribute.isPresent() && !loadedMainManifestInfo .getOriginalPackageName() .get() .equals(packageAttribute.get().getValue())) { // no suggestion for library since this is actually forbidden to change the // the package name per flavor. String message = mMergeType == MergeType.APPLICATION ? String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s\n" + "\tSuggestion: remove the overlay declaration at %5$s " + "\tand place it in the build.gradle:\n" + "\t\tflavorName {\n" + "\t\t\tapplicationId = \"%2$s\"\n" + "\t\t}", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition(), packageAttribute.get().getSourceLocation().print(true)) : String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition()); mergingReportBuilder.addMessage( overlayDocument.getXmlDocument().getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR, message); return mergingReportBuilder.build(); } overlayDocument .getXmlDocument() .getRootNode() .getXml() .setAttribute("package", mainPackageAttribute.get().getValue()); xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } } mLogger.info("Merging main manifest %s\n", mManifestFile.getPath()); xmlDocumentOptional = merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } // force main manifest package into resulting merged file when creating a library manifest. if (mMergeType == MergeType.LIBRARY) { // extract the package name... String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode().getXml().getAttribute("package"); // save it in the selector instance. if (!Strings.isNullOrEmpty(mainManifestPackageName)) { xmlDocumentOptional .get() .getRootNode() .getXml() .setAttribute("package", mainManifestPackageName); } } for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) { mLogger.info("Merging library manifest " + libraryDocument.getLocation()); xmlDocumentOptional = merge(xmlDocumentOptional, libraryDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } } // done with proper merging phase, now we need to trim unwanted elements, placeholder // substitution and system properties injection. ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { return mergingReportBuilder.build(); } // do one last placeholder substitution, this is useful as we don't stop the build // when a library failed a placeholder substitution, but the element might have // been overridden so the problem was transient. However, with the final document // ready, all placeholders values must have been provided. KeyBasedValueResolver<String> placeHolderValueResolver = new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues); PlaceholderHandler placeholderHandler = new PlaceholderHandler(); placeholderHandler.visit( mMergeType, xmlDocumentOptional.get(), placeHolderValueResolver, mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { return mergingReportBuilder.build(); } // perform system property injection. performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get()); XmlDocument finalMergedDocument = xmlDocumentOptional.get(); PostValidator.validate(finalMergedDocument, mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { finalMergedDocument .getRootNode() .addMessage( mergingReportBuilder, MergingReport.Record.Severity.WARNING, "Post merge validation failed"); } // only remove tools annotations if we are packaging an application. if (mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) { finalMergedDocument = ToolsInstructionsCleaner.cleanToolsReferences(finalMergedDocument, mLogger); } if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) { extractFcqns(finalMergedDocument); } if (finalMergedDocument != null) { mergingReportBuilder.setMergedDocument(finalMergedDocument); } MergingReport mergingReport = mergingReportBuilder.build(); StdLogger stdLogger = new StdLogger(StdLogger.Level.INFO); mergingReport.log(stdLogger); stdLogger.verbose(mergingReport.getMergedDocument().get().prettyPrint()); if (mReportFile.isPresent()) { writeReport(mergingReport); } return mergingReport; }
protected void displayInitError(String error) { mSdkLog.error(null /* Throwable */, "%s", error); // $NON-NLS-1$ }
/** * Reads the public.xml file in data/res/values/ for a given resource folder and builds up a map * of public resources. * * <p>This map is a subset of the full resource map that only contains framework resources that * are public. * * @param logger a logger to report issues to */ public void loadPublicResources(@Nullable ILogger logger) { IAbstractFolder valueFolder = getResFolder().getFolder(SdkConstants.FD_RES_VALUES); if (valueFolder.exists() == false) { return; } IAbstractFile publicXmlFile = valueFolder.getFile("public.xml"); // $NON-NLS-1$ if (publicXmlFile.exists()) { Reader reader = null; try { reader = new BufferedReader(new InputStreamReader(publicXmlFile.getContents(), Charsets.UTF_8)); KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(reader); ResourceType lastType = null; String lastTypeName = ""; while (true) { int event = parser.next(); if (event == XmlPullParser.START_TAG) { // As of API 15 there are a number of "java-symbol" entries here if (!parser.getName().equals("public")) { // $NON-NLS-1$ continue; } String name = null; String typeName = null; for (int i = 0, n = parser.getAttributeCount(); i < n; i++) { String attribute = parser.getAttributeName(i); if (attribute.equals("name")) { // $NON-NLS-1$ name = parser.getAttributeValue(i); if (typeName != null) { // Skip id attribute processing break; } } else if (attribute.equals("type")) { // $NON-NLS-1$ typeName = parser.getAttributeValue(i); } } if (name != null && typeName != null) { ResourceType type = null; if (typeName.equals(lastTypeName)) { type = lastType; } else { type = ResourceType.getEnum(typeName); lastType = type; lastTypeName = typeName; } if (type != null) { ResourceItem match = null; Map<String, ResourceItem> map = mResourceMap.get(type); if (map != null) { match = map.get(name); } if (match != null) { List<ResourceItem> publicList = mPublicResourceMap.get(type); if (publicList == null) { // Pick initial size for the list to hold the public // resources. We could just use map.size() here, // but they're usually much bigger; for example, // in one platform version, there are 1500 drawables // and 1200 strings but only 175 and 25 public ones // respectively. int size; switch (type) { case STYLE: size = 500; break; case ATTR: size = 1000; break; case DRAWABLE: size = 200; break; case ID: size = 50; break; case LAYOUT: case COLOR: case STRING: case ANIM: case INTERPOLATOR: size = 30; break; default: size = 10; break; } publicList = new ArrayList<ResourceItem>(size); mPublicResourceMap.put(type, publicList); } publicList.add(match); } else { // log that there's a public resource that doesn't actually // exist? } } else { // log that there was a reference to a typo that doesn't actually // exist? } } } else if (event == XmlPullParser.END_DOCUMENT) { break; } } } catch (Exception e) { if (logger != null) { logger.error(e, "Can't read and parse public attribute list"); } } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // Nothing to be done here - we don't care if it closed or not. } } } } // put unmodifiable list for all res type in the public resource map // this will simplify access for (ResourceType type : ResourceType.values()) { List<ResourceItem> list = mPublicResourceMap.get(type); if (list == null) { list = Collections.emptyList(); } else { list = Collections.unmodifiableList(list); } // put the new list in the map mPublicResourceMap.put(type, list); } }
@Override public void verbose(@NonNull String msgFormat, Object... args) { mLogger.verbose(msgFormat, args); }
@Override public void info(@NonNull String msgFormat, Object... args) { mLogger.info(msgFormat, args); }
@Override public void warning(@NonNull String msgFormat, Object... args) { mLogger.warning(mGradleMessageRewriter.rewriteMessages(msgFormat), args); }
@Override public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) { mLogger.error(t, mGradleMessageRewriter.rewriteMessages(Strings.nullToEmpty(msgFormat)), args); }
@Override public Boolean call() throws Exception { String deviceName = device.getName(); boolean isInstalled = false; CustomTestRunListener runListener = new CustomTestRunListener(deviceName, projectName, flavorName, logger); runListener.setReportDir(resultsDir); long time = System.currentTimeMillis(); boolean success = false; String coverageFile = "/data/data/" + testData.getTestedApplicationId() + "/" + FILE_COVERAGE_EC; try { device.connect(timeoutInMs, logger); if (!testedApks.isEmpty()) { logger.verbose( "DeviceConnector '%s': installing %s", deviceName, Joiner.on(',').join(testedApks)); if (testedApks.size() > 1 && device.getApiLevel() < 21) { throw new InstallException( "Internal error, file a bug, multi-apk applications" + " require a device with API level 21+"); } if (device.getApiLevel() >= 21) { device.installPackages( testedApks, ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger); } else { device.installPackage( testedApks.get(0), ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger); } } logger.verbose("DeviceConnector '%s': installing %s", deviceName, testApk); if (device.getApiLevel() >= 21) { device.installPackages( ImmutableList.of(testApk), ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger); } else { device.installPackage( testApk, ImmutableList.<String>of() /* installOptions */, timeoutInMs, logger); } isInstalled = true; RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner( testData.getApplicationId(), testData.getInstrumentationRunner(), device); for (Map.Entry<String, String> argument : testData.getInstrumentationRunnerArguments().entrySet()) { runner.addInstrumentationArg(argument.getKey(), argument.getValue()); } if (testData.isTestCoverageEnabled()) { runner.addInstrumentationArg("coverage", "true"); runner.addInstrumentationArg("coverageFile", coverageFile); } runner.setRunName(deviceName); runner.setMaxtimeToOutputResponse(timeoutInMs); runner.run(runListener); TestRunResult testRunResult = runListener.getRunResult(); success = true; // for now throw an exception if no tests. // TODO return a status instead of allow merging of multi-variants/multi-device reports. if (testRunResult.getNumTests() == 0) { CustomTestRunListener fakeRunListener = new CustomTestRunListener(deviceName, projectName, flavorName, logger); fakeRunListener.setReportDir(resultsDir); // create a fake test output Map<String, String> emptyMetrics = Collections.emptyMap(); TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "No tests found."); fakeRunListener.testStarted(fakeTest); fakeRunListener.testFailed( fakeTest, "No tests found. This usually means that your test classes are" + " not in the form that your test runner expects (e.g. don't inherit from" + " TestCase or lack @Test annotations)."); fakeRunListener.testEnded(fakeTest, emptyMetrics); // end the run to generate the XML file. fakeRunListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics); return false; } return !testRunResult.hasFailedTests(); } catch (Exception e) { Map<String, String> emptyMetrics = Collections.emptyMap(); // create a fake test output ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(baos, true); e.printStackTrace(pw); TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "runTests"); runListener.testStarted(fakeTest); runListener.testFailed(fakeTest, baos.toString()); runListener.testEnded(fakeTest, emptyMetrics); // end the run to generate the XML file. runListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics); // and throw throw e; } finally { if (isInstalled) { // Get the coverage if needed. if (success && testData.isTestCoverageEnabled()) { String temporaryCoverageCopy = "/data/local/tmp/" + testData.getTestedApplicationId() + "." + FILE_COVERAGE_EC; MultiLineReceiver outputReceiver = new MultiLineReceiver() { @Override public void processNewLines(String[] lines) { for (String line : lines) { logger.info(line); } } @Override public boolean isCancelled() { return false; } }; logger.verbose( "DeviceConnector '%s': fetching coverage data from %s", deviceName, coverageFile); device.executeShellCommand( "run-as " + testData.getTestedApplicationId() + " cat " + coverageFile + " | cat > " + temporaryCoverageCopy, outputReceiver, 30, TimeUnit.SECONDS); device.pullFile(temporaryCoverageCopy, new File(coverageDir, FILE_COVERAGE_EC).getPath()); device.executeShellCommand( "rm " + temporaryCoverageCopy, outputReceiver, 30, TimeUnit.SECONDS); } // uninstall the apps // This should really not be null, because if it was the build // would have broken before. uninstall(testApk, testData.getApplicationId(), deviceName); if (!testedApks.isEmpty()) { for (File testedApk : testedApks) { uninstall(testedApk, testData.getTestedApplicationId(), deviceName); } } } device.disconnect(timeoutInMs, logger); } }
/** * Validates that all archive licenses are accepted. * * <p>There are 2 cases: <br> * - When {@code acceptLicenses} is given, the licenses specified are automatically accepted and * all those not specified are automatically rejected. <br> * - When {@code acceptLicenses} is empty or null, licenses are collected and there's an input * prompt on StdOut to ask a yes/no question. To output, this uses the current {@link #mSdkLog} * which should be configured to send {@link ILogger#info(String, Object...)} directly to {@link * System#out}. <br> * Finally only accepted licenses are kept in the archive list. * * @param archives The archives to validate. * @param acceptLicenseIds A comma-separated list of licenses ids already approved. * @param numRetries The number of times the command-line will ask to accept a given license when * the input doesn't match the expected y/n/yes/no answer. Use 0 for infinite. Useful for * unit-tests. Once the number of retries is reached, the license is assumed as rejected. * @return True if there are any archives left to install. */ @VisibleForTesting(visibility = Visibility.PRIVATE) boolean acceptLicense(List<ArchiveInfo> archives, String acceptLicenseIds, final int numRetries) { TreeSet<String> acceptedLids = new TreeSet<String>(); if (acceptLicenseIds != null) { acceptedLids.addAll(Arrays.asList(acceptLicenseIds.split(","))); // $NON-NLS-1$ } boolean automated = !acceptedLids.isEmpty(); TreeSet<String> rejectedLids = new TreeSet<String>(); TreeMap<String, License> lidToAccept = new TreeMap<String, License>(); TreeMap<String, List<String>> lidPkgNames = new TreeMap<String, List<String>>(); // Find the licenses needed. Include those already accepted. for (ArchiveInfo ai : archives) { License lic = getArchiveInfoLicense(ai); if (lic == null) { continue; } String lid = getLicenseId(lic); if (!acceptedLids.contains(lid)) { if (automated) { // Automatically reject those not already accepted rejectedLids.add(lid); } else { // Queue it to ask for it to be accepted lidToAccept.put(lid, lic); List<String> list = lidPkgNames.get(lid); if (list == null) { list = new ArrayList<String>(); lidPkgNames.put(lid, list); } list.add(ai.getShortDescription()); } } } // Ask for each license that needs to be asked manually for confirmation nextEntry: for (Map.Entry<String, License> entry : lidToAccept.entrySet()) { String lid = entry.getKey(); License lic = entry.getValue(); mSdkLog.info("-------------------------------\n"); mSdkLog.info("License id: %1$s\n", lid); mSdkLog.info( "Used by: \n - %1$s\n", Joiner.on("\n - ").skipNulls().join(lidPkgNames.get(lid))); mSdkLog.info("-------------------------------\n\n"); mSdkLog.info("%1$s\n", lic.getLicense()); int retries = numRetries; tryAgain: while (true) { try { mSdkLog.info("Do you accept the license '%1$s' [y/n]: ", lid); byte[] buffer = new byte[256]; if (mSdkLog instanceof IReaderLogger) { ((IReaderLogger) mSdkLog).readLine(buffer); } else { System.in.read(buffer); } mSdkLog.info("\n"); String reply = new String(buffer, Charsets.UTF_8); reply = reply.trim().toLowerCase(Locale.US); if ("y".equals(reply) || "yes".equals(reply)) { acceptedLids.add(lid); continue nextEntry; } else if ("n".equals(reply) || "no".equals(reply)) { break tryAgain; } else { mSdkLog.info("Unknown response '%1$s'.\n", reply); if (--retries == 0) { mSdkLog.info("Max number of retries exceeded. Rejecting '%1$s'\n", lid); break tryAgain; } continue tryAgain; } } catch (IOException e) { // Panic. Don't install anything. e.printStackTrace(); return false; } } rejectedLids.add(lid); } // Finally remove all archive which license is rejected or not accepted. for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) { ArchiveInfo ai = it.next(); License lic = getArchiveInfoLicense(ai); if (lic == null) { continue; } String lid = getLicenseId(lic); if (rejectedLids.contains(lid) || !acceptedLids.contains(lid)) { mSdkLog.info( "Package %1$s not installed due to rejected license '%2$s'.\n", ai.getShortDescription(), lid); it.remove(); } } return !archives.isEmpty(); }