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