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