1 /* 2 * Copyright (C) 2011 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.tasks; 18 19 import com.android.sdklib.internal.repository.ITask; 20 import com.android.sdklib.internal.repository.ITaskMonitor; 21 import com.android.sdklib.internal.repository.UserCredentials; 22 import com.android.sdkuilib.ui.AuthenticationDialog; 23 import com.android.sdkuilib.ui.GridDialog; 24 25 import org.eclipse.jface.dialogs.MessageDialog; 26 import org.eclipse.swt.SWT; 27 import org.eclipse.swt.widgets.Control; 28 import org.eclipse.swt.widgets.Display; 29 import org.eclipse.swt.widgets.Event; 30 import org.eclipse.swt.widgets.Label; 31 import org.eclipse.swt.widgets.Listener; 32 import org.eclipse.swt.widgets.ProgressBar; 33 import org.eclipse.swt.widgets.Shell; 34 import org.eclipse.swt.widgets.Widget; 35 36 37 /** 38 * Implements a "view" that uses an existing progress bar, status button and 39 * status text to display a {@link ITaskMonitor}. 40 */ 41 public final class ProgressView implements IProgressUiProvider { 42 43 private static enum State { 44 /** View created but there's no task running. Next state can only be ACTIVE. */ 45 IDLE, 46 /** A task is currently running. Next state is either STOP_PENDING or IDLE. */ 47 ACTIVE, 48 /** Stop button has been clicked. Waiting for thread to finish. Next state is IDLE. */ 49 STOP_PENDING, 50 } 51 52 /** The current mode of operation of the dialog. */ 53 private State mState = State.IDLE; 54 55 56 57 // UI fields 58 private final Label mLabel; 59 private final Control mStopButton; 60 private final ProgressBar mProgressBar; 61 62 /** Logger object. Cannot not be null. */ 63 private final ILogUiProvider mLog; 64 65 /** 66 * Creates a new {@link ProgressView} object, a simple "holder" for the various 67 * widgets used to display and update a progress + status bar. 68 * 69 * @param label The label to display titles of status updates (e.g. task titles and 70 * calls to {@link #setDescription(String)}.) Must not be null. 71 * @param progressBar The progress bar to update during a task. Must not be null. 72 * @param stopButton The stop button. It will be disabled when there's no task that can 73 * be interrupted. A selection listener will be attached to it. Optional. Can be null. 74 * @param log A <em>mandatory</em> logger object that will be used to report all the log. 75 * Must not be null. 76 */ 77 public ProgressView( 78 Label label, 79 ProgressBar progressBar, 80 Control stopButton, 81 ILogUiProvider log) { 82 mLabel = label; 83 mProgressBar = progressBar; 84 mLog = log; 85 mProgressBar.setEnabled(false); 86 87 mStopButton = stopButton; 88 if (mStopButton != null) { 89 mStopButton.addListener(SWT.Selection, new Listener() { 90 @Override 91 public void handleEvent(Event event) { 92 if (mState == State.ACTIVE) { 93 changeState(State.STOP_PENDING); 94 } 95 } 96 }); 97 } 98 } 99 100 /** 101 * Starts the task and block till it's either finished or canceled. 102 * This can be called from a non-UI thread safely. 103 */ 104 public void startTask( 105 final String title, 106 final ITaskMonitor parentMonitor, 107 final ITask task) { 108 if (task != null) { 109 try { 110 if (parentMonitor == null && !mProgressBar.isDisposed()) { 111 mLabel.setText(title); 112 mProgressBar.setSelection(0); 113 mProgressBar.setEnabled(true); 114 changeState(ProgressView.State.ACTIVE); 115 } 116 117 Runnable r = new Runnable() { 118 @Override 119 public void run() { 120 if (parentMonitor == null) { 121 task.run(new TaskMonitorImpl(ProgressView.this)); 122 123 } else { 124 // Use all the reminder of the parent monitor. 125 if (parentMonitor.getProgressMax() == 0) { 126 parentMonitor.setProgressMax(1); 127 } 128 ITaskMonitor sub = parentMonitor.createSubMonitor( 129 parentMonitor.getProgressMax() - parentMonitor.getProgress()); 130 try { 131 task.run(sub); 132 } finally { 133 int delta = 134 sub.getProgressMax() - sub.getProgress(); 135 if (delta > 0) { 136 sub.incProgress(delta); 137 } 138 } 139 } 140 } 141 }; 142 143 // If for some reason the UI has been disposed, just abort the thread. 144 if (mProgressBar.isDisposed()) { 145 return; 146 } 147 148 if (TaskMonitorImpl.isTaskMonitorImpl(parentMonitor)) { 149 // If there's a parent monitor and it's our own class, we know this parent 150 // is already running a thread and the base one is running an event loop. 151 // We should thus not run a second event loop and we can process the 152 // runnable right here instead of spawning a thread inside the thread. 153 r.run(); 154 155 } else { 156 // No parent monitor. This is the first one so we need a thread and 157 // we need to process UI events. 158 159 final Thread t = new Thread(r, title); 160 t.start(); 161 162 // Process the app's event loop whilst we wait for the thread to finish 163 while (!mProgressBar.isDisposed() && t.isAlive()) { 164 Display display = mProgressBar.getDisplay(); 165 if (!mProgressBar.isDisposed() && !display.readAndDispatch()) { 166 display.sleep(); 167 } 168 } 169 } 170 } catch (Exception e) { 171 // TODO log 172 173 } finally { 174 if (parentMonitor == null && !mProgressBar.isDisposed()) { 175 changeState(ProgressView.State.IDLE); 176 mProgressBar.setSelection(0); 177 mProgressBar.setEnabled(false); 178 } 179 } 180 } 181 } 182 183 private void syncExec(final Widget widget, final Runnable runnable) { 184 if (widget != null && !widget.isDisposed()) { 185 widget.getDisplay().syncExec(new Runnable() { 186 @Override 187 public void run() { 188 // Check again whether the widget got disposed between the time where 189 // we requested the syncExec and the time it actually happened. 190 if (!widget.isDisposed()) { 191 runnable.run(); 192 } 193 } 194 }); 195 } 196 } 197 198 private void changeState(State state) { 199 if (mState != null ) { 200 mState = state; 201 } 202 203 syncExec(mStopButton, new Runnable() { 204 @Override 205 public void run() { 206 mStopButton.setEnabled(mState == State.ACTIVE); 207 } 208 }); 209 210 } 211 212 // --- Implementation of ITaskUiProvider --- 213 214 @Override 215 public boolean isCancelRequested() { 216 return mState != State.ACTIVE; 217 } 218 219 /** 220 * Sets the description in the current task dialog. 221 * This method can be invoked from a non-UI thread. 222 */ 223 @Override 224 public void setDescription(final String description) { 225 syncExec(mLabel, new Runnable() { 226 @Override 227 public void run() { 228 mLabel.setText(description); 229 } 230 }); 231 232 mLog.setDescription(description); 233 } 234 235 /** 236 * Logs a "normal" information line. 237 * This method can be invoked from a non-UI thread. 238 */ 239 @Override 240 public void log(String log) { 241 mLog.log(log); 242 } 243 244 /** 245 * Logs an "error" information line. 246 * This method can be invoked from a non-UI thread. 247 */ 248 @Override 249 public void logError(String log) { 250 mLog.logError(log); 251 } 252 253 /** 254 * Logs a "verbose" information line, that is extra details which are typically 255 * not that useful for the end-user and might be hidden until explicitly shown. 256 * This method can be invoked from a non-UI thread. 257 */ 258 @Override 259 public void logVerbose(String log) { 260 mLog.logVerbose(log); 261 } 262 263 /** 264 * Sets the max value of the progress bar. 265 * This method can be invoked from a non-UI thread. 266 * 267 * @see ProgressBar#setMaximum(int) 268 */ 269 @Override 270 public void setProgressMax(final int max) { 271 syncExec(mProgressBar, new Runnable() { 272 @Override 273 public void run() { 274 mProgressBar.setMaximum(max); 275 } 276 }); 277 } 278 279 /** 280 * Sets the current value of the progress bar. 281 * This method can be invoked from a non-UI thread. 282 */ 283 @Override 284 public void setProgress(final int value) { 285 syncExec(mProgressBar, new Runnable() { 286 @Override 287 public void run() { 288 mProgressBar.setSelection(value); 289 } 290 }); 291 } 292 293 /** 294 * Returns the current value of the progress bar, 295 * between 0 and up to {@link #setProgressMax(int)} - 1. 296 * This method can be invoked from a non-UI thread. 297 */ 298 @Override 299 public int getProgress() { 300 final int[] result = new int[] { 0 }; 301 302 if (!mProgressBar.isDisposed()) { 303 mProgressBar.getDisplay().syncExec(new Runnable() { 304 @Override 305 public void run() { 306 if (!mProgressBar.isDisposed()) { 307 result[0] = mProgressBar.getSelection(); 308 } 309 } 310 }); 311 } 312 313 return result[0]; 314 } 315 316 @Override 317 public boolean displayPrompt(final String title, final String message) { 318 final boolean[] result = new boolean[] { false }; 319 320 syncExec(mProgressBar, new Runnable() { 321 @Override 322 public void run() { 323 Shell shell = mProgressBar.getShell(); 324 result[0] = MessageDialog.openQuestion(shell, title, message); 325 } 326 }); 327 328 return result[0]; 329 } 330 331 /** 332 * This method opens a pop-up window which requests for User Credentials. 333 * 334 * @param title The title of the window. 335 * @param message The message to displayed in the login/password window. 336 * @return Returns user provided credentials. 337 * If operation is <b>canceled</b> by user the return value must be <b>null</b>. 338 * @see ITaskMonitor#displayLoginCredentialsPrompt(String, String) 339 */ 340 @Override 341 public UserCredentials 342 displayLoginCredentialsPrompt(final String title, final String message) { 343 final String[] resultArray = new String[] {"", "", "", ""}; 344 // open dialog and request login and password 345 syncExec(mProgressBar, new Runnable() { 346 @Override 347 public void run() { 348 Shell shell = mProgressBar.getShell(); 349 AuthenticationDialog authenticationDialog = new AuthenticationDialog(shell, 350 title, 351 message); 352 int dlgResult = authenticationDialog.open(); 353 if (dlgResult == GridDialog.OK) { 354 resultArray[0] = authenticationDialog.getLogin(); 355 resultArray[1] = authenticationDialog.getPassword(); 356 resultArray[2] = authenticationDialog.getWorkstation(); 357 resultArray[3] = authenticationDialog.getDomain(); 358 } 359 } 360 }); 361 362 return new UserCredentials(resultArray[0], 363 resultArray[1], 364 resultArray[2], 365 resultArray[3]); 366 } 367 } 368 369