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