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.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