/* * Copyright (C) 2010 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.internal.repository.updater; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkManager; import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITaskFactory; import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.NullTaskMonitor; import com.android.sdklib.internal.repository.UserCredentials; import com.android.sdklib.internal.repository.archives.Archive; import com.android.sdklib.repository.SdkRepoConstants; import com.android.utils.ILogger; import com.android.utils.IReaderLogger; import com.android.utils.NullLogger; import com.android.utils.Pair; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; /** * Performs an update using only a non-interactive console output with no GUI. * * @deprecated * com.android.sdklib.internal.repository has moved into Studio as * com.android.tools.idea.sdk.remote.internal. */ @Deprecated public class SdkUpdaterNoWindow { /** The {@link UpdaterData} to use. */ private final UpdaterData mUpdaterData; /** The {@link ILogger} logger to use. */ private final ILogger mSdkLog; /** The reply to any question asked by the update process. Currently this will * be yes/no for ability to replace modified samples or restart ADB. */ private final boolean mForce; /** * Creates an UpdateNoWindow object that will update using the given SDK root * and outputs to the given SDK logger. * * @param osSdkRoot The OS path of the SDK folder to update. * @param sdkManager An existing SDK manager to list current platforms and addons. * @param sdkLog A logger object, that should ideally output to a write-only console. * @param force The reply to any question asked by the update process. Currently this will * be yes/no for ability to replace modified samples or restart ADB. * @param useHttp True to force using HTTP instead of HTTPS for downloads. * @param proxyPort An optional HTTP/HTTPS proxy port. Can be null. * @param proxyHost An optional HTTP/HTTPS proxy host. Can be null. */ public SdkUpdaterNoWindow(String osSdkRoot, SdkManager sdkManager, ILogger sdkLog, boolean force, boolean useHttp, String proxyHost, String proxyPort) { mSdkLog = sdkLog; mForce = force; mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); // Read and apply settings from settings file, so that http/https proxy is set // and let the command line args override them as necessary. SettingsController settingsController = mUpdaterData.getSettingsController(); settingsController.loadSettings(); settingsController.applySettings(); setupProxy(proxyHost, proxyPort); // Change the in-memory settings to force the http/https mode settingsController.setSetting(ISettingsPage.KEY_FORCE_HTTP, useHttp); // Use a factory that only outputs to the given ILogger. mUpdaterData.setTaskFactory(new ConsoleTaskFactory()); // Check that the AVD Manager has been correctly initialized. This is done separately // from the constructor in the GUI-based UpdaterWindowImpl to give time to the UI to // initialize before displaying a message box. Since we don't have any GUI here // we can call it whenever we want. if (mUpdaterData.checkIfInitFailed()) { return; } // Setup the default sources including the getenv overrides. mUpdaterData.setupDefaultSources(); mUpdaterData.getLocalSdkParser().parseSdk( osSdkRoot, sdkManager, new NullTaskMonitor(sdkLog)); } /** * Performs the actual update. * * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages * we can update. 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. * @param acceptLicense SDK licenses to automatically accept. * @deprecated Use {@link #updateAll(java.util.ArrayList, boolean, boolean, String, boolean)} * instead */ @Deprecated public void updateAll( ArrayList<String> pkgFilter, boolean includeAll, boolean dryMode, String acceptLicense) { updateAll(pkgFilter, includeAll, dryMode, acceptLicense, false); } /** * Performs the actual update. * * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages * we can update. 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. * @param acceptLicense SDK licenses to automatically accept. * @param includeDependencies If true, also include any required dependencies */ public void updateAll( ArrayList<String> pkgFilter, boolean includeAll, boolean dryMode, String acceptLicense, boolean includeDependencies) { mUpdaterData.updateOrInstallAll_NoGUI(pkgFilter, includeAll, dryMode, acceptLicense, includeDependencies); } /** * Lists remote packages available for install using 'android update sdk --no-ui'. * * @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(boolean includeAll, boolean extendedOutput) { mUpdaterData.listRemotePackages_NoGUI(includeAll, extendedOutput); } /** * Installs a platform package given its target hash string. * <p/> * This does not work for add-ons right now, just basic platforms. * * @param hashString The hash string of the platform to install. * @return A boolean indicating whether the installation was successful (meaning the package * was either already present, or got installed or updated properly) and a {@link File} * with the path to the root folder of the package. The file is null when the boolean * is false, otherwise it should point to an existing valid folder. */ public Pair<Boolean, File> installPlatformPackage(String hashString) { // TODO right now we really need the caller to use a reader-logger to // handle license confirmations. This isn't optimal and should be addressed // when we provide a proper UI for this. assert mSdkLog instanceof IReaderLogger; SdkManager sm = mUpdaterData.getSdkManager(); IAndroidTarget target = sm.getTargetFromHashString(hashString); if (target == null) { // Otherwise try to install it. // This currently only works for platforms since the package's "install id" // is an exactly match with the IAndroidTarget hash string. ArrayList<String> filter = new ArrayList<String>(); filter.add(hashString); List<Archive> installed = mUpdaterData.updateOrInstallAll_NoGUI( filter, true, //includeAll false, //dryMode null); //acceptLicense if (installed != null) { sm.reloadSdk(new NullLogger()); target = sm.getTargetFromHashString(hashString); } } if (target != null) { // Return existing target return Pair.of(Boolean.TRUE, new File(target.getLocation())); } return null; } // ----- /** * Sets both the HTTP and HTTPS proxy system properties, overriding the ones * from the settings with these values if they are defined. */ private void setupProxy(String proxyHost, String proxyPort) { // The system property constants can be found in the Java SE documentation at // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$ final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$ Properties props = System.getProperties(); if (proxyHost != null && !proxyHost.isEmpty()) { props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost); props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost); } if (proxyPort != null && !proxyPort.isEmpty()) { props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort); props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort); } } /** * A custom implementation of {@link ITaskFactory} that * provides {@link ConsoleTaskMonitor} objects. */ private class ConsoleTaskFactory implements ITaskFactory { @Override public void start(String title, ITask task) { start(title, null /*parentMonitor*/, task); } @Override public void start(String title, ITaskMonitor parentMonitor, ITask task) { if (parentMonitor == null) { task.run(new ConsoleTaskMonitor(title, task)); } else { // Use all the reminder of the parent monitor. if (parentMonitor.getProgressMax() == 0) { parentMonitor.setProgressMax(1); } ITaskMonitor sub = parentMonitor.createSubMonitor( parentMonitor.getProgressMax() - parentMonitor.getProgress()); try { task.run(sub); } finally { int delta = sub.getProgressMax() - sub.getProgress(); if (delta > 0) { sub.incProgress(delta); } } } } } /** * A custom implementation of {@link ITaskMonitor} that defers all output to the * super {@link SdkUpdaterNoWindow#mSdkLog}. */ private class ConsoleTaskMonitor implements ITaskMonitor { private static final double MAX_COUNT = 10000.0; private double mIncCoef = 0; private double mValue = 0; private String mLastDesc = null; private String mLastProgressBase = null; /** * Creates a new {@link ConsoleTaskMonitor} with the given title. */ public ConsoleTaskMonitor(String title, ITask task) { mSdkLog.info("%s:\n", title); } /** * Sets the description in the current task dialog. */ @Override public void setDescription(String format, Object...args) { String last = mLastDesc; String line = String.format(" " + format, args); //$NON-NLS-1$ // If the description contains a %, it generally indicates a recurring // progress so we want a \r at the end. int pos = line.indexOf('%'); if (pos > -1) { String base = line.trim(); if (mLastProgressBase != null && base.startsWith(mLastProgressBase)) { line = " " + base.substring(mLastProgressBase.length()); //$NON-NLS-1$ } line += '\r'; } else { mLastProgressBase = line.trim(); line += '\n'; } // Skip line if it's the same as the last one. if (last != null && last.equals(line.trim())) { return; } mLastDesc = line.trim(); // If the last line terminated with a \r but the new one doesn't, we need to // insert a \n to avoid erasing the previous line. if (last != null && last.endsWith("\r") && //$NON-NLS-1$ !line.endsWith("\r")) { //$NON-NLS-1$ line = '\n' + line; } mSdkLog.info("%s", line); //$NON-NLS-1$ } @Override public void log(String format, Object...args) { setDescription(" " + format, args); //$NON-NLS-1$ } @Override public void logError(String format, Object...args) { setDescription(format, args); } @Override public void logVerbose(String format, Object...args) { // The ConsoleTask does not display verbose log messages. } // --- ILogger --- @Override public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) { mSdkLog.error(t, errorFormat, args); } @Override public void warning(@NonNull String warningFormat, Object... args) { mSdkLog.warning(warningFormat, args); } @Override public void info(@NonNull String msgFormat, Object... args) { mSdkLog.info(msgFormat, args); } @Override public void verbose(@NonNull String msgFormat, Object... args) { mSdkLog.verbose(msgFormat, args); } /** * Sets the max value of the progress bar. * * Weird things will happen if setProgressMax is called multiple times * *after* {@link #incProgress(int)}: we don't try to adjust it on the * fly. */ @Override public void setProgressMax(int max) { assert max > 0; // Always set the dialog's progress max to 10k since it only handles // integers and we want to have a better inner granularity. Instead // we use the max to compute a coefficient for inc deltas. mIncCoef = max > 0 ? MAX_COUNT / max : 0; assert mIncCoef > 0; } @Override public int getProgressMax() { return mIncCoef > 0 ? (int) (MAX_COUNT / mIncCoef) : 0; } /** * Increments the current value of the progress bar. */ @Override public void incProgress(int delta) { if (delta > 0 && mIncCoef > 0) { internalIncProgress(delta * mIncCoef); } } private void internalIncProgress(double realDelta) { mValue += realDelta; // max value is 10k, so 10k/100 == 100%. // Experimentation shows that it is not really useful to display this // progression since during download the description line will change. // mSdkLog.printf(" [%3d%%]\r", ((int)mValue) / 100); } /** * Returns the current value of the progress bar, * between 0 and up to {@link #setProgressMax(int)} - 1. */ @Override public int getProgress() { assert mIncCoef > 0; return mIncCoef > 0 ? (int)(mValue / mIncCoef) : 0; } /** * Returns true if the "Cancel" button was selected. */ @Override public boolean isCancelRequested() { return false; } /** * Display a yes/no question dialog box. * * This implementation allow this to be called from any thread, it * makes sure the dialog is opened synchronously in the ui thread. * * @param title The title of the dialog box * @param message The error message * @return true if YES was clicked. */ @Override public boolean displayPrompt(final String title, final String message) { // TODO Make it interactive if mForce==false mSdkLog.info("\n%1$s\n%2$s\n%3$s", //$NON-NLS-1$ title, message, mForce ? "--force used, will reply yes\n" : "Note: you can use --force to override to yes.\n"); if (mForce) { return true; } while (true) { mSdkLog.info("%1$s", "[y/n] =>"); //$NON-NLS-1$ try { byte[] readBuffer = new byte[2048]; String reply = readLine(readBuffer).trim(); mSdkLog.info("\n"); //$NON-NLS-1$ if (!reply.isEmpty() && reply.length() <= 3) { char c = reply.charAt(0); if (c == 'y' || c == 'Y') { return true; } else if (c == 'n' || c == 'N') { return false; } } mSdkLog.info("Unknown reply '%s'. Please use y[es]/n[o].\n"); //$NON-NLS-1$ } catch (IOException e) { // Exception. Be conservative and say no. mSdkLog.info("\n"); //$NON-NLS-1$ return false; } } } /** * Displays a prompt message to the user and read two values, * login/password. * <p> * <i>Asks user for login/password information.</i> * <p> * This method shows a question in the standard output, asking for login * and password.</br> * <b>Method Output:</b></br> * Title</br> * Message</br> * Login: (Wait for user input)</br> * Password: (Wait for user input)</br> * <p> * * @param title The title of the iteration. * @param message The message to be displayed. * @return A {@link Pair} holding the entered login and password. The * <b>first element</b> is always the <b>Login</b>, and the * <b>second element</b> is always the <b>Password</b>. This * method will never return null, in case of error the pair will * be filled with empty strings. * @see ITaskMonitor#displayLoginCredentialsPrompt(String, String) */ @Override public UserCredentials displayLoginCredentialsPrompt(String title, String message) { String login = ""; //$NON-NLS-1$ String password = ""; //$NON-NLS-1$ String workstation = ""; //$NON-NLS-1$ String domain = ""; //$NON-NLS-1$ mSdkLog.info("\n%1$s\n%2$s", title, message); byte[] readBuffer = new byte[2048]; try { mSdkLog.info("\nLogin: "); login = readLine(readBuffer); mSdkLog.info("\nPassword: "); password = readLine(readBuffer); mSdkLog.info("\nIf your proxy uses NTLM authentication, provide the following information. Leave blank otherwise."); mSdkLog.info("\nWorkstation: "); workstation = readLine(readBuffer); mSdkLog.info("\nDomain: "); domain = readLine(readBuffer); /* * TODO: Implement a way to don't echo the typed password On * Java 5 there's no simple way to do this. There's just a * workaround which is output backspaces on each keystroke. * A good alternative is to use Java 6 java.io.Console */ } catch (IOException e) { // Reset login/pass to empty Strings. login = ""; //$NON-NLS-1$ password = ""; //$NON-NLS-1$ workstation = ""; //$NON-NLS-1$ domain = ""; //$NON-NLS-1$ //Just print the error to console. mSdkLog.info("\nError occurred during login/pass query: %s\n", e.getMessage()); } return new UserCredentials(login, password, workstation, domain); } /** * Reads current console input in the given buffer. * * @param buffer Buffer to hold the user input. Must be larger than the largest * expected input. Cannot be null. * @return A new string. May be empty but not null. * @throws IOException in case the buffer isn't long enough. */ private String readLine(byte[] buffer) throws IOException { int count; if (mSdkLog instanceof IReaderLogger) { count = ((IReaderLogger) mSdkLog).readLine(buffer); } else { count = System.in.read(buffer); } // is the input longer than the buffer? if (count == buffer.length && buffer[count-1] != 10) { throw new IOException(String.format( "Input is longer than the buffer size, (%1$s) bytes", buffer.length)); } // ignore end whitespace while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) { count--; } return new String(buffer, 0, count); } /** * Creates a sub-monitor that will use up to tickCount on the progress bar. * tickCount must be 1 or more. */ @Override public ITaskMonitor createSubMonitor(int tickCount) { assert mIncCoef > 0; assert tickCount > 0; return new ConsoleSubTaskMonitor(this, null, mValue, tickCount * mIncCoef); } } private interface IConsoleSubTaskMonitor extends ITaskMonitor { void subIncProgress(double realDelta); } private static class ConsoleSubTaskMonitor implements IConsoleSubTaskMonitor { private final ConsoleTaskMonitor mRoot; private final IConsoleSubTaskMonitor mParent; private final double mStart; private final double mSpan; private double mSubValue; private double mSubCoef; /** * Creates a new sub task monitor which will work for the given range [start, start+span] * in its parent. * * @param root The ProgressTask root * @param parent The immediate parent. Can be the null or another sub task monitor. * @param start The start value in the root's coordinates * @param span The span value in the root's coordinates */ public ConsoleSubTaskMonitor(ConsoleTaskMonitor root, IConsoleSubTaskMonitor parent, double start, double span) { mRoot = root; mParent = parent; mStart = start; mSpan = span; mSubValue = start; } @Override public boolean isCancelRequested() { return mRoot.isCancelRequested(); } @Override public void setDescription(String format, Object... args) { mRoot.setDescription(format, args); } @Override public void log(String format, Object... args) { mRoot.log(format, args); } @Override public void logError(String format, Object... args) { mRoot.logError(format, args); } @Override public void logVerbose(String format, Object... args) { mRoot.logVerbose(format, args); } @Override public void setProgressMax(int max) { assert max > 0; mSubCoef = max > 0 ? mSpan / max : 0; assert mSubCoef > 0; } @Override public int getProgressMax() { return mSubCoef > 0 ? (int) (mSpan / mSubCoef) : 0; } @Override public int getProgress() { assert mSubCoef > 0; return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0; } @Override public void incProgress(int delta) { if (delta > 0 && mSubCoef > 0) { subIncProgress(delta * mSubCoef); } } @Override public void subIncProgress(double realDelta) { mSubValue += realDelta; if (mParent != null) { mParent.subIncProgress(realDelta); } else { mRoot.internalIncProgress(realDelta); } } @Override public boolean displayPrompt(String title, String message) { return mRoot.displayPrompt(title, message); } @Override public UserCredentials displayLoginCredentialsPrompt(String title, String message) { return mRoot.displayLoginCredentialsPrompt(title, message); } @Override public ITaskMonitor createSubMonitor(int tickCount) { assert mSubCoef > 0; assert tickCount > 0; return new ConsoleSubTaskMonitor(mRoot, this, mSubValue, tickCount * mSubCoef); } // --- ILogger --- @Override public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) { mRoot.error(t, errorFormat, args); } @Override public void warning(@NonNull String warningFormat, Object... args) { mRoot.warning(warningFormat, args); } @Override public void info(@NonNull String msgFormat, Object... args) { mRoot.info(msgFormat, args); } @Override public void verbose(@NonNull String msgFormat, Object... args) { mRoot.verbose(msgFormat, args); } } }