1 /* 2 * Copyright (C) 2010 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 com.android.sdkuilib.internal.repository; 18 19 import com.android.sdklib.ISdkLog; 20 import com.android.sdklib.SdkManager; 21 import com.android.sdklib.internal.repository.ITask; 22 import com.android.sdklib.internal.repository.ITaskFactory; 23 import com.android.sdklib.internal.repository.ITaskMonitor; 24 import com.android.sdklib.internal.repository.NullTaskMonitor; 25 import com.android.sdklib.repository.SdkRepoConstants; 26 import com.android.util.Pair; 27 28 import java.io.IOException; 29 import java.util.ArrayList; 30 import java.util.Properties; 31 32 /** 33 * Performs an update using only a non-interactive console output with no GUI. 34 */ 35 public class SdkUpdaterNoWindow { 36 37 /** The {@link UpdaterData} to use. */ 38 private final UpdaterData mUpdaterData; 39 /** The {@link ISdkLog} logger to use. */ 40 private final ISdkLog mSdkLog; 41 /** The reply to any question asked by the update process. Currently this will 42 * be yes/no for ability to replace modified samples or restart ADB. */ 43 private final boolean mForce; 44 45 /** 46 * Creates an UpdateNoWindow object that will update using the given SDK root 47 * and outputs to the given SDK logger. 48 * 49 * @param osSdkRoot The OS path of the SDK folder to update. 50 * @param sdkManager An existing SDK manager to list current platforms and addons. 51 * @param sdkLog A logger object, that should ideally output to a write-only console. 52 * @param force The reply to any question asked by the update process. Currently this will 53 * be yes/no for ability to replace modified samples or restart ADB. 54 * @param useHttp True to force using HTTP instead of HTTPS for downloads. 55 * @param proxyPort An optional HTTP/HTTPS proxy port. Can be null. 56 * @param proxyHost An optional HTTP/HTTPS proxy host. Can be null. 57 */ SdkUpdaterNoWindow(String osSdkRoot, SdkManager sdkManager, ISdkLog sdkLog, boolean force, boolean useHttp, String proxyHost, String proxyPort)58 public SdkUpdaterNoWindow(String osSdkRoot, 59 SdkManager sdkManager, 60 ISdkLog sdkLog, 61 boolean force, 62 boolean useHttp, 63 String proxyHost, 64 String proxyPort) { 65 mSdkLog = sdkLog; 66 mForce = force; 67 mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); 68 69 // Read and apply settings from settings file, so that http/https proxy is set 70 // and let the command line args override them as necessary. 71 SettingsController settingsController = mUpdaterData.getSettingsController(); 72 settingsController.loadSettings(); 73 settingsController.applySettings(); 74 setupProxy(proxyHost, proxyPort); 75 76 // Change the in-memory settings to force the http/https mode 77 settingsController.setSetting(ISettingsPage.KEY_FORCE_HTTP, useHttp); 78 79 // Use a factory that only outputs to the given ISdkLog. 80 mUpdaterData.setTaskFactory(new ConsoleTaskFactory()); 81 82 // Check that the AVD Manager has been correctly initialized. This is done separately 83 // from the constructor in the GUI-based UpdaterWindowImpl to give time to the UI to 84 // initialize before displaying a message box. Since we don't have any GUI here 85 // we can call it whenever we want. 86 if (mUpdaterData.checkIfInitFailed()) { 87 return; 88 } 89 90 // Setup the default sources including the getenv overrides. 91 mUpdaterData.setupDefaultSources(); 92 93 mUpdaterData.getLocalSdkParser().parseSdk( 94 osSdkRoot, 95 sdkManager, 96 new NullTaskMonitor(sdkLog)); 97 } 98 99 /** 100 * Performs the actual update. 101 * 102 * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages 103 * we can update. A null or empty list means to update everything possible. 104 * @param includeObsoletes True to also list and install obsolete packages. 105 * @param dryMode True to check what would be updated/installed but do not actually 106 * download or install anything. 107 */ updateAll( ArrayList<String> pkgFilter, boolean includeObsoletes, boolean dryMode)108 public void updateAll( 109 ArrayList<String> pkgFilter, 110 boolean includeObsoletes, 111 boolean dryMode) { 112 mUpdaterData.updateOrInstallAll_NoGUI(pkgFilter, includeObsoletes, dryMode); 113 } 114 115 /** 116 * Lists remote packages available for install using 'android update sdk --no-ui'. 117 * 118 * @param includeObsoletes True to also list and install obsolete packages. 119 * @param extendedOutput True to display more details on each package. 120 */ listRemotePackages(boolean includeObsoletes, boolean extendedOutput)121 public void listRemotePackages(boolean includeObsoletes, boolean extendedOutput) { 122 mUpdaterData.listRemotePackages_NoGUI(includeObsoletes, extendedOutput); 123 } 124 125 // ----- 126 127 /** 128 * Sets both the HTTP and HTTPS proxy system properties, overriding the ones 129 * from the settings with these values if they are defined. 130 */ setupProxy(String proxyHost, String proxyPort)131 private void setupProxy(String proxyHost, String proxyPort) { 132 133 // The system property constants can be found in the Java SE documentation at 134 // http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html 135 final String JAVA_PROP_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ 136 final String JAVA_PROP_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ 137 final String JAVA_PROP_HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$ 138 final String JAVA_PROP_HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$ 139 140 Properties props = System.getProperties(); 141 142 if (proxyHost != null && proxyHost.length() > 0) { 143 props.setProperty(JAVA_PROP_HTTP_PROXY_HOST, proxyHost); 144 props.setProperty(JAVA_PROP_HTTPS_PROXY_HOST, proxyHost); 145 } 146 if (proxyPort != null && proxyPort.length() > 0) { 147 props.setProperty(JAVA_PROP_HTTP_PROXY_PORT, proxyPort); 148 props.setProperty(JAVA_PROP_HTTPS_PROXY_PORT, proxyPort); 149 } 150 } 151 152 /** 153 * A custom implementation of {@link ITaskFactory} that 154 * provides {@link ConsoleTaskMonitor} objects. 155 */ 156 private class ConsoleTaskFactory implements ITaskFactory { start(String title, ITask task)157 public void start(String title, ITask task) { 158 start(title, null /*parentMonitor*/, task); 159 } 160 start(String title, ITaskMonitor parentMonitor, ITask task)161 public void start(String title, ITaskMonitor parentMonitor, ITask task) { 162 if (parentMonitor == null) { 163 task.run(new ConsoleTaskMonitor(title, task)); 164 } else { 165 // Use all the reminder of the parent monitor. 166 if (parentMonitor.getProgressMax() == 0) { 167 parentMonitor.setProgressMax(1); 168 } 169 170 ITaskMonitor sub = parentMonitor.createSubMonitor( 171 parentMonitor.getProgressMax() - parentMonitor.getProgress()); 172 try { 173 task.run(sub); 174 } finally { 175 int delta = 176 sub.getProgressMax() - sub.getProgress(); 177 if (delta > 0) { 178 sub.incProgress(delta); 179 } 180 } 181 } 182 } 183 } 184 185 /** 186 * A custom implementation of {@link ITaskMonitor} that defers all output to the 187 * super {@link SdkUpdaterNoWindow#mSdkLog}. 188 */ 189 private class ConsoleTaskMonitor implements ITaskMonitor { 190 191 private static final double MAX_COUNT = 10000.0; 192 private double mIncCoef = 0; 193 private double mValue = 0; 194 private String mLastDesc = null; 195 private String mLastProgressBase = null; 196 197 /** 198 * Creates a new {@link ConsoleTaskMonitor} with the given title. 199 */ ConsoleTaskMonitor(String title, ITask task)200 public ConsoleTaskMonitor(String title, ITask task) { 201 mSdkLog.printf("%s:\n", title); 202 } 203 204 /** 205 * Sets the description in the current task dialog. 206 */ setDescription(String format, Object...args)207 public void setDescription(String format, Object...args) { 208 209 String last = mLastDesc; 210 String line = String.format(" " + format, args); //$NON-NLS-1$ 211 212 // If the description contains a %, it generally indicates a recurring 213 // progress so we want a \r at the end. 214 int pos = line.indexOf('%'); 215 if (pos > -1) { 216 String base = line.trim(); 217 if (mLastProgressBase != null && base.startsWith(mLastProgressBase)) { 218 line = " " + base.substring(mLastProgressBase.length()); //$NON-NLS-1$ 219 } 220 line += '\r'; 221 } else { 222 mLastProgressBase = line.trim(); 223 line += '\n'; 224 } 225 226 // Skip line if it's the same as the last one. 227 if (last != null && last.equals(line.trim())) { 228 return; 229 } 230 mLastDesc = line.trim(); 231 232 // If the last line terminated with a \r but the new one doesn't, we need to 233 // insert a \n to avoid erasing the previous line. 234 if (last != null && 235 last.endsWith("\r") && //$NON-NLS-1$ 236 !line.endsWith("\r")) { //$NON-NLS-1$ 237 line = '\n' + line; 238 } 239 240 mSdkLog.printf("%s", line); //$NON-NLS-1$ 241 } 242 log(String format, Object...args)243 public void log(String format, Object...args) { 244 setDescription(" " + format, args); //$NON-NLS-1$ 245 } 246 logError(String format, Object...args)247 public void logError(String format, Object...args) { 248 setDescription(format, args); 249 } 250 logVerbose(String format, Object...args)251 public void logVerbose(String format, Object...args) { 252 // The ConsoleTask does not display verbose log messages. 253 } 254 255 // --- ISdkLog --- 256 error(Throwable t, String errorFormat, Object... args)257 public void error(Throwable t, String errorFormat, Object... args) { 258 mSdkLog.error(t, errorFormat, args); 259 } 260 warning(String warningFormat, Object... args)261 public void warning(String warningFormat, Object... args) { 262 mSdkLog.warning(warningFormat, args); 263 } 264 printf(String msgFormat, Object... args)265 public void printf(String msgFormat, Object... args) { 266 mSdkLog.printf(msgFormat, args); 267 } 268 269 /** 270 * Sets the max value of the progress bar. 271 * 272 * Weird things will happen if setProgressMax is called multiple times 273 * *after* {@link #incProgress(int)}: we don't try to adjust it on the 274 * fly. 275 */ setProgressMax(int max)276 public void setProgressMax(int max) { 277 assert max > 0; 278 // Always set the dialog's progress max to 10k since it only handles 279 // integers and we want to have a better inner granularity. Instead 280 // we use the max to compute a coefficient for inc deltas. 281 mIncCoef = max > 0 ? MAX_COUNT / max : 0; 282 assert mIncCoef > 0; 283 } 284 getProgressMax()285 public int getProgressMax() { 286 return mIncCoef > 0 ? (int) (MAX_COUNT / mIncCoef) : 0; 287 } 288 289 /** 290 * Increments the current value of the progress bar. 291 */ incProgress(int delta)292 public void incProgress(int delta) { 293 if (delta > 0 && mIncCoef > 0) { 294 internalIncProgress(delta * mIncCoef); 295 } 296 } 297 internalIncProgress(double realDelta)298 private void internalIncProgress(double realDelta) { 299 mValue += realDelta; 300 // max value is 10k, so 10k/100 == 100%. 301 // Experimentation shows that it is not really useful to display this 302 // progression since during download the description line will change. 303 // mSdkLog.printf(" [%3d%%]\r", ((int)mValue) / 100); 304 } 305 306 /** 307 * Returns the current value of the progress bar, 308 * between 0 and up to {@link #setProgressMax(int)} - 1. 309 */ getProgress()310 public int getProgress() { 311 assert mIncCoef > 0; 312 return mIncCoef > 0 ? (int)(mValue / mIncCoef) : 0; 313 } 314 315 /** 316 * Returns true if the "Cancel" button was selected. 317 */ isCancelRequested()318 public boolean isCancelRequested() { 319 return false; 320 } 321 322 /** 323 * Display a yes/no question dialog box. 324 * 325 * This implementation allow this to be called from any thread, it 326 * makes sure the dialog is opened synchronously in the ui thread. 327 * 328 * @param title The title of the dialog box 329 * @param message The error message 330 * @return true if YES was clicked. 331 */ displayPrompt(final String title, final String message)332 public boolean displayPrompt(final String title, final String message) { 333 // TODO Make it interactive if mForce==false 334 mSdkLog.printf("\n%s\n%s\n[y/n] => %s\n", 335 title, 336 message, 337 mForce ? "yes" : "no (use --force to override)"); 338 return mForce; 339 } 340 341 /** 342 * Displays a prompt message to the user and read two values, 343 * login/password. 344 * <p> 345 * <i>Asks user for login/password information.</i> 346 * <p> 347 * This method shows a question in the standard output, asking for login 348 * and password.</br> 349 * <b>Method Output:</b></br> 350 * Title</br> 351 * Message</br> 352 * Login: (Wait for user input)</br> 353 * Password: (Wait for user input)</br> 354 * <p> 355 * 356 * @param title The title of the iteration. 357 * @param message The message to be displayed. 358 * @return A {@link Pair} holding the entered login and password. The 359 * <b>first element</b> is always the <b>Login</b>, and the 360 * <b>second element</b> is always the <b>Password</b>. This 361 * method will never return null, in case of error the pair will 362 * be filled with empty strings. 363 * @see ITaskMonitor#displayLoginPasswordPrompt(String, String) 364 */ displayLoginPasswordPrompt(String title, String message)365 public Pair<String, String> displayLoginPasswordPrompt(String title, String message) { 366 String login = ""; //$NON-NLS-1$ 367 String password = ""; //$NON-NLS-1$ 368 mSdkLog.printf("\n%1$s\n%2$s", title, message); 369 byte[] readBuffer = new byte[2048]; 370 try { 371 mSdkLog.printf("\nLogin: "); 372 login = readLine(readBuffer); 373 mSdkLog.printf("\nPassword: "); 374 password = readLine(readBuffer); 375 /* 376 * TODO: Implement a way to don't echo the typed password On 377 * Java 5 there's no simple way to do this. There's just a 378 * workaround which is output backspaces on each keystroke. 379 * A good alternative is to use Java 6 java.io.Console 380 */ 381 } catch (IOException e) { 382 // Reset login/pass to empty Strings. 383 login = ""; //$NON-NLS-1$ 384 password = ""; //$NON-NLS-1$ 385 //Just print the error to console. 386 mSdkLog.printf("\nError occurred during login/pass query: %s\n", e.getMessage()); 387 } 388 389 return Pair.of(login, password); 390 } 391 readLine(byte[] buffer)392 private String readLine(byte[] buffer) throws IOException { 393 int count = System.in.read(buffer); 394 395 // is the input longer than the buffer? 396 if (count == buffer.length && buffer[count-1] != 10) { 397 throw new IOException(String.format( 398 "Input is longer than the buffer size, (%1$s) bytes", buffer.length)); 399 } 400 401 // ignore end whitespace 402 while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) { 403 count--; 404 } 405 406 return new String(buffer, 0, count); 407 } 408 409 /** 410 * Creates a sub-monitor that will use up to tickCount on the progress bar. 411 * tickCount must be 1 or more. 412 */ createSubMonitor(int tickCount)413 public ITaskMonitor createSubMonitor(int tickCount) { 414 assert mIncCoef > 0; 415 assert tickCount > 0; 416 return new ConsoleSubTaskMonitor(this, null, mValue, tickCount * mIncCoef); 417 } 418 } 419 420 private interface IConsoleSubTaskMonitor extends ITaskMonitor { subIncProgress(double realDelta)421 public void subIncProgress(double realDelta); 422 } 423 424 private static class ConsoleSubTaskMonitor implements IConsoleSubTaskMonitor { 425 426 private final ConsoleTaskMonitor mRoot; 427 private final IConsoleSubTaskMonitor mParent; 428 private final double mStart; 429 private final double mSpan; 430 private double mSubValue; 431 private double mSubCoef; 432 433 /** 434 * Creates a new sub task monitor which will work for the given range [start, start+span] 435 * in its parent. 436 * 437 * @param root The ProgressTask root 438 * @param parent The immediate parent. Can be the null or another sub task monitor. 439 * @param start The start value in the root's coordinates 440 * @param span The span value in the root's coordinates 441 */ ConsoleSubTaskMonitor(ConsoleTaskMonitor root, IConsoleSubTaskMonitor parent, double start, double span)442 public ConsoleSubTaskMonitor(ConsoleTaskMonitor root, 443 IConsoleSubTaskMonitor parent, 444 double start, 445 double span) { 446 mRoot = root; 447 mParent = parent; 448 mStart = start; 449 mSpan = span; 450 mSubValue = start; 451 } 452 isCancelRequested()453 public boolean isCancelRequested() { 454 return mRoot.isCancelRequested(); 455 } 456 setDescription(String format, Object... args)457 public void setDescription(String format, Object... args) { 458 mRoot.setDescription(format, args); 459 } 460 log(String format, Object... args)461 public void log(String format, Object... args) { 462 mRoot.log(format, args); 463 } 464 logError(String format, Object... args)465 public void logError(String format, Object... args) { 466 mRoot.logError(format, args); 467 } 468 logVerbose(String format, Object... args)469 public void logVerbose(String format, Object... args) { 470 mRoot.logVerbose(format, args); 471 } 472 setProgressMax(int max)473 public void setProgressMax(int max) { 474 assert max > 0; 475 mSubCoef = max > 0 ? mSpan / max : 0; 476 assert mSubCoef > 0; 477 } 478 getProgressMax()479 public int getProgressMax() { 480 return mSubCoef > 0 ? (int) (mSpan / mSubCoef) : 0; 481 } 482 getProgress()483 public int getProgress() { 484 assert mSubCoef > 0; 485 return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0; 486 } 487 incProgress(int delta)488 public void incProgress(int delta) { 489 if (delta > 0 && mSubCoef > 0) { 490 subIncProgress(delta * mSubCoef); 491 } 492 } 493 subIncProgress(double realDelta)494 public void subIncProgress(double realDelta) { 495 mSubValue += realDelta; 496 if (mParent != null) { 497 mParent.subIncProgress(realDelta); 498 } else { 499 mRoot.internalIncProgress(realDelta); 500 } 501 } 502 displayPrompt(String title, String message)503 public boolean displayPrompt(String title, String message) { 504 return mRoot.displayPrompt(title, message); 505 } 506 displayLoginPasswordPrompt(String title, String message)507 public Pair<String, String> displayLoginPasswordPrompt(String title, String message) { 508 return mRoot.displayLoginPasswordPrompt(title, message); 509 } 510 createSubMonitor(int tickCount)511 public ITaskMonitor createSubMonitor(int tickCount) { 512 assert mSubCoef > 0; 513 assert tickCount > 0; 514 return new ConsoleSubTaskMonitor(mRoot, 515 this, 516 mSubValue, 517 tickCount * mSubCoef); 518 } 519 520 // --- ISdkLog --- 521 error(Throwable t, String errorFormat, Object... args)522 public void error(Throwable t, String errorFormat, Object... args) { 523 mRoot.error(t, errorFormat, args); 524 } 525 warning(String warningFormat, Object... args)526 public void warning(String warningFormat, Object... args) { 527 mRoot.warning(warningFormat, args); 528 } 529 printf(String msgFormat, Object... args)530 public void printf(String msgFormat, Object... args) { 531 mRoot.printf(msgFormat, args); 532 } 533 } 534 } 535