Home | History | Annotate | Download | only in repository
      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