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.repository.SdkRepository; 25 26 import java.util.ArrayList; 27 28 /** 29 * Performs an update using only a non-interactive console output with no GUI. 30 * <p/> 31 * TODO: It may be useful in the future to let the filter specify packages names 32 * rather than package types, typically to let the user upgrade to a new platform. 33 * This can be achieved easily by simply allowing package names in the pkgFilter 34 * argument. 35 */ 36 public class UpdateNoWindow { 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 */ UpdateNoWindow(String osSdkRoot, SdkManager sdkManager, ISdkLog sdkLog, boolean force, boolean useHttp)57 public UpdateNoWindow(String osSdkRoot, 58 SdkManager sdkManager, 59 ISdkLog sdkLog, 60 boolean force, 61 boolean useHttp) { 62 mSdkLog = sdkLog; 63 mForce = force; 64 mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); 65 66 // Change the in-memory settings to force the http/https mode 67 mUpdaterData.getSettingsController().setSetting(ISettingsPage.KEY_FORCE_HTTP, useHttp); 68 69 // Use a factory that only outputs to the given ISdkLog. 70 mUpdaterData.setTaskFactory(new ConsoleTaskFactory()); 71 72 // Check that the AVD Manager has been correctly initialized. This is done separately 73 // from the constructor in the GUI-based UpdaterWindowImpl to give time to the UI to 74 // initialize before displaying a message box. Since we don't have any GUI here 75 // we can call it whenever we want. 76 if (mUpdaterData.checkIfInitFailed()) { 77 return; 78 } 79 80 // Setup the default sources including the getenv overrides. 81 mUpdaterData.setupDefaultSources(); 82 83 mUpdaterData.getLocalSdkParser().parseSdk(osSdkRoot, sdkManager, sdkLog); 84 85 } 86 87 /** 88 * Performs the actual update. 89 * 90 * @param pkgFilter A list of {@link SdkRepository#NODES} to limit the type of packages 91 * we can update. A null or empty list means to update everything possible. 92 * @param includeObsoletes True to also list and install obsolete packages. 93 * @param dryMode True to check what would be updated/installed but do not actually 94 * download or install anything. 95 */ updateAll( ArrayList<String> pkgFilter, boolean includeObsoletes, boolean dryMode)96 public void updateAll( 97 ArrayList<String> pkgFilter, 98 boolean includeObsoletes, 99 boolean dryMode) { 100 mUpdaterData.updateOrInstallAll_NoGUI(pkgFilter, includeObsoletes, dryMode); 101 } 102 103 /** 104 * A custom implementation of {@link ITaskFactory} that provides {@link ConsoleTask} objects. 105 */ 106 private class ConsoleTaskFactory implements ITaskFactory { start(String title, ITask task)107 public void start(String title, ITask task) { 108 new ConsoleTask(title, task); 109 } 110 } 111 112 /** 113 * A custom implementation of {@link ITaskMonitor} that defers all output to the 114 * super {@link UpdateNoWindow#mSdkLog}. 115 */ 116 private class ConsoleTask implements ITaskMonitor { 117 118 private static final double MAX_COUNT = 10000.0; 119 private double mIncCoef = 0; 120 private double mValue = 0; 121 private String mLastDesc = null; 122 private String mLastProgressBase = null; 123 124 /** 125 * Creates a new {@link ConsoleTask} with the given title. 126 */ ConsoleTask(String title, ITask task)127 public ConsoleTask(String title, ITask task) { 128 mSdkLog.printf("%s:\n", title); 129 task.run(this); 130 } 131 132 /** 133 * Sets the description in the current task dialog. 134 */ setDescription(String descriptionFormat, Object...args)135 public void setDescription(String descriptionFormat, Object...args) { 136 137 String last = mLastDesc; 138 String line = String.format(" " + descriptionFormat, args); 139 140 // If the description contains a %, it generally indicates a recurring 141 // progress so we want a \r at the end. 142 if (line.indexOf('%') > -1) { 143 if (mLastProgressBase != null && line.startsWith(mLastProgressBase)) { 144 line = " " + line.substring(mLastProgressBase.length()); 145 } 146 line += "\r"; 147 } else { 148 mLastProgressBase = line; 149 line += "\n"; 150 } 151 152 // Skip line if it's the same as the last one. 153 if (last != null && last.equals(line)) { 154 return; 155 } 156 mLastDesc = line; 157 158 // If the last line terminated with a \r but the new one doesn't, we need to 159 // insert a \n to avoid erasing the previous line. 160 if (last != null && 161 last.endsWith("\r") && 162 !line.endsWith("\r")) { 163 line = "\n" + line; 164 } 165 166 mSdkLog.printf("%s", line); 167 } 168 169 /** 170 * Sets the description in the current task dialog. 171 */ setResult(String resultFormat, Object...args)172 public void setResult(String resultFormat, Object...args) { 173 setDescription(resultFormat, args); 174 } 175 176 /** 177 * Sets the max value of the progress bar. 178 * 179 * Weird things will happen if setProgressMax is called multiple times 180 * *after* {@link #incProgress(int)}: we don't try to adjust it on the 181 * fly. 182 */ setProgressMax(int max)183 public void setProgressMax(int max) { 184 assert max > 0; 185 // Always set the dialog's progress max to 10k since it only handles 186 // integers and we want to have a better inner granularity. Instead 187 // we use the max to compute a coefficient for inc deltas. 188 mIncCoef = max > 0 ? MAX_COUNT / max : 0; 189 assert mIncCoef > 0; 190 } 191 192 /** 193 * Increments the current value of the progress bar. 194 */ incProgress(int delta)195 public void incProgress(int delta) { 196 assert mIncCoef > 0; 197 assert delta > 0; 198 internalIncProgress(delta * mIncCoef); 199 } 200 internalIncProgress(double realDelta)201 private void internalIncProgress(double realDelta) { 202 mValue += realDelta; 203 // max value is 10k, so 10k/100 == 100%. 204 // Experimentation shows that it is not really useful to display this 205 // progression since during download the description line will change. 206 // mSdkLog.printf(" [%3d%%]\r", ((int)mValue) / 100); 207 } 208 209 /** 210 * Returns the current value of the progress bar, 211 * between 0 and up to {@link #setProgressMax(int)} - 1. 212 */ getProgress()213 public int getProgress() { 214 assert mIncCoef > 0; 215 return mIncCoef > 0 ? (int)(mValue / mIncCoef) : 0; 216 } 217 218 /** 219 * Returns true if the "Cancel" button was selected. 220 */ isCancelRequested()221 public boolean isCancelRequested() { 222 return false; 223 } 224 225 /** 226 * Display a yes/no question dialog box. 227 * 228 * This implementation allow this to be called from any thread, it 229 * makes sure the dialog is opened synchronously in the ui thread. 230 * 231 * @param title The title of the dialog box 232 * @param message The error message 233 * @return true if YES was clicked. 234 */ displayPrompt(final String title, final String message)235 public boolean displayPrompt(final String title, final String message) { 236 // TODO Make it interactive if mForce==false 237 mSdkLog.printf("\n%s\n%s\n[y/n] => %s\n", 238 title, 239 message, 240 mForce ? "yes" : "no (use --force to override)"); 241 return mForce; 242 } 243 244 /** 245 * Creates a sub-monitor that will use up to tickCount on the progress bar. 246 * tickCount must be 1 or more. 247 */ createSubMonitor(int tickCount)248 public ITaskMonitor createSubMonitor(int tickCount) { 249 assert mIncCoef > 0; 250 assert tickCount > 0; 251 return new ConsoleSubTaskMonitor(this, null, mValue, tickCount * mIncCoef); 252 } 253 } 254 255 private interface IConsoleSubTaskMonitor extends ITaskMonitor { subIncProgress(double realDelta)256 public void subIncProgress(double realDelta); 257 } 258 259 private static class ConsoleSubTaskMonitor implements IConsoleSubTaskMonitor { 260 261 private final ConsoleTask mRoot; 262 private final IConsoleSubTaskMonitor mParent; 263 private final double mStart; 264 private final double mSpan; 265 private double mSubValue; 266 private double mSubCoef; 267 268 /** 269 * Creates a new sub task monitor which will work for the given range [start, start+span] 270 * in its parent. 271 * 272 * @param root The ProgressTask root 273 * @param parent The immediate parent. Can be the null or another sub task monitor. 274 * @param start The start value in the root's coordinates 275 * @param span The span value in the root's coordinates 276 */ ConsoleSubTaskMonitor(ConsoleTask root, IConsoleSubTaskMonitor parent, double start, double span)277 public ConsoleSubTaskMonitor(ConsoleTask root, 278 IConsoleSubTaskMonitor parent, 279 double start, 280 double span) { 281 mRoot = root; 282 mParent = parent; 283 mStart = start; 284 mSpan = span; 285 mSubValue = start; 286 } 287 isCancelRequested()288 public boolean isCancelRequested() { 289 return mRoot.isCancelRequested(); 290 } 291 setDescription(String descriptionFormat, Object... args)292 public void setDescription(String descriptionFormat, Object... args) { 293 mRoot.setDescription(descriptionFormat, args); 294 } 295 setResult(String resultFormat, Object... args)296 public void setResult(String resultFormat, Object... args) { 297 mRoot.setResult(resultFormat, args); 298 } 299 setProgressMax(int max)300 public void setProgressMax(int max) { 301 assert max > 0; 302 mSubCoef = max > 0 ? mSpan / max : 0; 303 assert mSubCoef > 0; 304 } 305 getProgress()306 public int getProgress() { 307 assert mSubCoef > 0; 308 return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0; 309 } 310 incProgress(int delta)311 public void incProgress(int delta) { 312 assert mSubCoef > 0; 313 subIncProgress(delta * mSubCoef); 314 } 315 subIncProgress(double realDelta)316 public void subIncProgress(double realDelta) { 317 mSubValue += realDelta; 318 if (mParent != null) { 319 mParent.subIncProgress(realDelta); 320 } else { 321 mRoot.internalIncProgress(realDelta); 322 } 323 } 324 displayPrompt(String title, String message)325 public boolean displayPrompt(String title, String message) { 326 return mRoot.displayPrompt(title, message); 327 } 328 createSubMonitor(int tickCount)329 public ITaskMonitor createSubMonitor(int tickCount) { 330 assert mSubCoef > 0; 331 assert tickCount > 0; 332 return new ConsoleSubTaskMonitor(mRoot, 333 this, 334 mSubValue, 335 tickCount * mSubCoef); 336 } 337 } 338 } 339