• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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