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 */ 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 */ 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 */ 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 */ 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 { 157 public void start(String title, ITask task) { 158 start(title, null /*parentMonitor*/, task); 159 } 160 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 */ 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 */ 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 243 public void log(String format, Object...args) { 244 setDescription(" " + format, args); //$NON-NLS-1$ 245 } 246 247 public void logError(String format, Object...args) { 248 setDescription(format, args); 249 } 250 251 public void logVerbose(String format, Object...args) { 252 // The ConsoleTask does not display verbose log messages. 253 } 254 255 // --- ISdkLog --- 256 257 public void error(Throwable t, String errorFormat, Object... args) { 258 mSdkLog.error(t, errorFormat, args); 259 } 260 261 public void warning(String warningFormat, Object... args) { 262 mSdkLog.warning(warningFormat, args); 263 } 264 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 */ 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 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 */ 292 public void incProgress(int delta) { 293 if (delta > 0 && mIncCoef > 0) { 294 internalIncProgress(delta * mIncCoef); 295 } 296 } 297 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 { 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 */ 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 453 public boolean isCancelRequested() { 454 return mRoot.isCancelRequested(); 455 } 456 457 public void setDescription(String format, Object... args) { 458 mRoot.setDescription(format, args); 459 } 460 461 public void log(String format, Object... args) { 462 mRoot.log(format, args); 463 } 464 465 public void logError(String format, Object... args) { 466 mRoot.logError(format, args); 467 } 468 469 public void logVerbose(String format, Object... args) { 470 mRoot.logVerbose(format, args); 471 } 472 473 public void setProgressMax(int max) { 474 assert max > 0; 475 mSubCoef = max > 0 ? mSpan / max : 0; 476 assert mSubCoef > 0; 477 } 478 479 public int getProgressMax() { 480 return mSubCoef > 0 ? (int) (mSpan / mSubCoef) : 0; 481 } 482 483 public int getProgress() { 484 assert mSubCoef > 0; 485 return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0; 486 } 487 488 public void incProgress(int delta) { 489 if (delta > 0 && mSubCoef > 0) { 490 subIncProgress(delta * mSubCoef); 491 } 492 } 493 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 503 public boolean displayPrompt(String title, String message) { 504 return mRoot.displayPrompt(title, message); 505 } 506 507 public Pair<String, String> displayLoginPasswordPrompt(String title, String message) { 508 return mRoot.displayLoginPasswordPrompt(title, message); 509 } 510 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 522 public void error(Throwable t, String errorFormat, Object... args) { 523 mRoot.error(t, errorFormat, args); 524 } 525 526 public void warning(String warningFormat, Object... args) { 527 mRoot.warning(warningFormat, args); 528 } 529 530 public void printf(String msgFormat, Object... args) { 531 mRoot.printf(msgFormat, args); 532 } 533 } 534 } 535