SDK Updater: Refactor, polish and complete the progress dialog.
This splits the former ProgressTask in 2 parts:
ProgressDialog is just the SWT dialog and ProgressTask
is the task handling.
This helps avoiding confusing SWT Designer each time
I do a change in the logic. Plus it seems cleaner anyway.
The other thing this CL does is implement the pause/close
I originally wanted: the pause button is greyed once used
by the user whilst the task completes. After, it becomes
a "Close" button if there's a result text displayed.
Closing the window using the close box is now the same as
using the pause/close button (to avoid closing the dialog
with a running thread.)
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
index 3f32ddc..6e9cef8 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
@@ -226,7 +226,7 @@
private void onUpdateInstalledPackage() {
// TODO just a test, needs to be removed later.
- ProgressTask.start(getShell(), "Test", new ITask() {
+ new ProgressTask(getShell(), "Test", new ITask() {
public void run(ITaskMonitor monitor) {
monitor.setDescription("Test");
monitor.setProgressMax(100);
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java
new file mode 100755
index 0000000..b1f5da7
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.internal.repository.ITaskMonitor;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+
+
+/**
+ * Implements a {@link ProgressDialog}, used by the {@link ProgressTask} class.
+ * This separates the dialog UI from the task logic.
+ *
+ * Note: this does not implement the {@link ITaskMonitor} interface to avoid confusing
+ * SWT Designer.
+ */
+final class ProgressDialog extends Dialog {
+
+ private enum CancelMode {
+ /** Cancel button says "Cancel" and is enabled. Waiting for user to cancel. */
+ ACTIVE,
+ /** Cancel button has been clicked. Waiting for thread to finish. */
+ CANCEL_PENDING,
+ /** Close pending. Close button clicked or thread finished but there were some
+ * messages so the user needs to manually close. */
+ CLOSE_MANUAL,
+ /** Close button clicked or thread finished. The window will automatically close. */
+ CLOSE_AUTO
+ }
+
+ /** The current mode of operation of the dialog. */
+ private CancelMode mCancelMode = CancelMode.ACTIVE;
+
+ /** Last dialog size for this session. */
+ private static Point sLastSize;
+
+
+ // UI fields
+ private Shell mDialogShell;
+ private Composite mRootComposite;
+ private Label mLabel;
+ private ProgressBar mProgressBar;
+ private Button mCancelButton;
+ private Text mResultText;
+ private final Thread mTaskThread;
+
+
+ /**
+ * Create the dialog.
+ * @param parent Parent container
+ * @param taskThread The thread to run the task.
+ */
+ public ProgressDialog(Shell parent, Thread taskThread) {
+ super(parent, SWT.APPLICATION_MODAL);
+ mTaskThread = taskThread;
+ }
+
+ /**
+ * Open the dialog and blocks till it gets closed
+ */
+ public void open() {
+ createContents();
+ positionShell(); //$hide$ (hide from SWT designer)
+ mDialogShell.open();
+ mDialogShell.layout();
+
+ startThread(); //$hide$ (hide from SWT designer)
+
+ Display display = getParent().getDisplay();
+ while (!mDialogShell.isDisposed() && mCancelMode != CancelMode.CLOSE_AUTO) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+ setCancelRequested(); //$hide$ (hide from SWT designer)
+
+ if (!mDialogShell.isDisposed()) {
+ sLastSize = mDialogShell.getSize();
+ mDialogShell.close();
+ }
+ }
+
+ /**
+ * Create contents of the dialog.
+ */
+ private void createContents() {
+ mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE);
+ mDialogShell.addShellListener(new ShellAdapter() {
+ @Override
+ public void shellClosed(ShellEvent e) {
+ onShellClosed(e);
+ }
+ });
+ mDialogShell.setLayout(new GridLayout(1, false));
+ mDialogShell.setSize(450, 300);
+ mDialogShell.setText(getText());
+
+ mRootComposite = new Composite(mDialogShell, SWT.NONE);
+ mRootComposite.setLayout(new GridLayout(2, false));
+ mRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ mLabel = new Label(mRootComposite, SWT.NONE);
+ mLabel.setText("Task");
+ mLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+
+ mProgressBar = new ProgressBar(mRootComposite, SWT.NONE);
+ mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mCancelButton = new Button(mRootComposite, SWT.NONE);
+ mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mCancelButton.setText("Cancel");
+
+ mCancelButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onCancelSelected(); //$hide$
+ }
+ });
+
+ mResultText = new Text(mRootComposite,
+ SWT.BORDER | SWT.READ_ONLY | SWT.V_SCROLL | SWT.MULTI);
+ mResultText.setEditable(true);
+ mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ }
+
+ // -- End of UI, Start of internal logic ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ public boolean isCancelRequested() {
+ return mCancelMode != CancelMode.ACTIVE;
+ }
+
+ /**
+ * Sets the mode to cancel pending.
+ * The first time this grays the cancel button, to let the user know that the
+ * cancel operation is pending.
+ */
+ public void setCancelRequested() {
+ if (!mDialogShell.isDisposed()) {
+ // The dialog is not disposed, make sure to run all this in the UI thread
+ // and lock on the cancel button mode.
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+
+ public void run() {
+ synchronized (mCancelMode) {
+ if (mCancelMode == CancelMode.ACTIVE) {
+ mCancelMode = CancelMode.CANCEL_PENDING;
+
+ if (!mCancelButton.isDisposed()) {
+ mCancelButton.setEnabled(false);
+ }
+ }
+ }
+ }
+ });
+ } else {
+ // The dialog is disposed. Just set the boolean. We shouldn't be here.
+ if (mCancelMode == CancelMode.ACTIVE) {
+ mCancelMode = CancelMode.CANCEL_PENDING;
+ }
+ }
+ }
+
+ /**
+ * Sets the mode to close manual.
+ * The first time, this also ungrays the pause button and converts it to a close button.
+ */
+ public void setManualCloseRequested() {
+ if (!mDialogShell.isDisposed()) {
+ // The dialog is not disposed, make sure to run all this in the UI thread
+ // and lock on the cancel button mode.
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+
+ public void run() {
+ synchronized (mCancelMode) {
+ if (mCancelMode != CancelMode.CLOSE_MANUAL &&
+ mCancelMode != CancelMode.CLOSE_AUTO) {
+ mCancelMode = CancelMode.CLOSE_MANUAL;
+
+ if (!mCancelButton.isDisposed()) {
+ mCancelButton.setEnabled(true);
+ mCancelButton.setText("Close");
+ }
+ }
+ }
+ }
+ });
+ } else {
+ // The dialog is disposed. Just set the booleans. We shouldn't be here.
+ if (mCancelMode != CancelMode.CLOSE_MANUAL &&
+ mCancelMode != CancelMode.CLOSE_AUTO) {
+ mCancelMode = CancelMode.CLOSE_MANUAL;
+ }
+ }
+ }
+
+ /**
+ * Sets the mode to close auto.
+ * The main loop will just exit and close the shell at the first opportunity.
+ */
+ public void setAutoCloseRequested() {
+ synchronized (mCancelMode) {
+ if (mCancelMode != CancelMode.CLOSE_AUTO) {
+ mCancelMode = CancelMode.CLOSE_AUTO;
+ }
+ }
+ }
+
+ /**
+ * Callback invoked when the cancel button is selected.
+ * When in closing mode, this simply closes the shell. Otherwise triggers a cancel.
+ */
+ private void onCancelSelected() {
+ if (mCancelMode == CancelMode.CLOSE_MANUAL) {
+ setAutoCloseRequested();
+ } else {
+ setCancelRequested();
+ }
+ }
+
+ /**
+ * Callback invoked when the shell is closed either by clicking the close button
+ * on by calling shell.close().
+ * This does the same thing as clicking the cancel/close button unless the mode is
+ * to auto close in which case we should do nothing to let the shell close normally.
+ */
+ private void onShellClosed(ShellEvent e) {
+ if (mCancelMode != CancelMode.CLOSE_AUTO) {
+ e.doit = false; // don't close directly
+ onCancelSelected();
+ }
+ }
+
+ /**
+ * Sets the description in the current task dialog.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setDescription(final String descriptionFormat, final Object...args) {
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mLabel.isDisposed()) {
+ mLabel.setText(String.format(descriptionFormat, args));
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the description in the current task dialog.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setResult(final String resultFormat, final Object...args) {
+ if (!mDialogShell.isDisposed()) {
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mResultText.isDisposed()) {
+ mResultText.setVisible(true);
+ mResultText.setText(String.format(resultFormat, args));
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Sets the max value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ *
+ * @see ProgressBar#setMaximum(int)
+ */
+ public void setProgressMax(final int max) {
+ if (!mDialogShell.isDisposed()) {
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mProgressBar.isDisposed()) {
+ mProgressBar.setMaximum(max);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Increments the current value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void incProgress(final int delta) {
+ if (!mDialogShell.isDisposed()) {
+ mDialogShell.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mProgressBar.isDisposed()) {
+ mProgressBar.setSelection(mProgressBar.getSelection() + delta);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts the thread that runs the task.
+ * This is deferred till the UI is created.
+ */
+ private void startThread() {
+ if (mTaskThread != null) {
+ mTaskThread.start();
+ }
+ }
+
+ /**
+ * Centers the dialog in its parent shell.
+ */
+ private void positionShell() {
+ // Centers the dialog in its parent shell
+ Shell child = mDialogShell;
+ Shell parent = getParent();
+ if (child != null && parent != null) {
+
+ // get the parent client area with a location relative to the display
+ Rectangle parentArea = parent.getClientArea();
+ Point parentLoc = parent.getLocation();
+ int px = parentLoc.x;
+ int py = parentLoc.y;
+ int pw = parentArea.width;
+ int ph = parentArea.height;
+
+ // Reuse the last size if there's one, otherwise use the default
+ Point childSize = sLastSize != null ? sLastSize : child.getSize();
+ int cw = childSize.x;
+ int ch = childSize.y;
+
+ child.setLocation(px + (pw - cw) / 2, py + (ph - ch) / 2);
+ child.setSize(cw, ch);
+ }
+ }
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
index 7667355..b2599ef 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
@@ -19,133 +19,29 @@
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Dialog;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-/*
- * TODO:
- * - trap window.close and treat it as a cancel request
- * - on cancel as been clicked *and* the task finished,, change it to a "close" button
+
+/**
+ * An {@link ITaskMonitor} that displays a {@link ProgressDialog}.
*/
+class ProgressTask implements ITaskMonitor {
-
-class ProgressTask extends Dialog
- implements ITaskMonitor //$hide$ (hide from SWT designer)
- {
-
- private boolean mCancelRequested;
- private boolean mCloseRequested;
+ private ProgressDialog mDialog;
private boolean mAutomaticallyCloseOnTaskCompletion = true;
- // UI fields
- private Shell mDialogShell;
- private Composite mRootComposite;
- private Label mLabel;
- private ProgressBar mProgressBar;
- private Button mCancelButton;
- private Text mResultText;
-
-
- /**
- * Create the dialog.
- * @param parent Parent container
- */
- public ProgressTask(Shell parent) {
- super(parent, SWT.APPLICATION_MODAL);
- }
-
- /**
- * Open the dialog and blocks till it gets closed
- */
- public void open() {
- createContents();
- mDialogShell.open();
- mDialogShell.layout();
- Display display = getParent().getDisplay();
-
- startTask(); //$hide$ (hide from SWT designer)
-
- while (!mDialogShell.isDisposed() && !mCloseRequested) {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- }
-
- mCancelRequested = true;
-
- if (!mDialogShell.isDisposed()) {
- mDialogShell.close();
- }
- }
-
- /**
- * Create contents of the dialog.
- */
- private void createContents() {
- mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
- mDialogShell.setLayout(new GridLayout(1, false));
- mDialogShell.setSize(450, 300);
- mDialogShell.setText(getText());
-
- mRootComposite = new Composite(mDialogShell, SWT.NONE);
- mRootComposite.setLayout(new GridLayout(2, false));
- mRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
-
- mLabel = new Label(mRootComposite, SWT.NONE);
- mLabel.setText("Task");
- mLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
-
- mProgressBar = new ProgressBar(mRootComposite, SWT.NONE);
- mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- mCancelButton = new Button(mRootComposite, SWT.NONE);
- mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
- mCancelButton.setText("Cancel");
-
- mCancelButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- mCancelRequested = true;
- mCancelButton.setEnabled(false);
- }
- });
-
- mResultText = new Text(mRootComposite,
- SWT.BORDER | SWT.READ_ONLY | SWT.V_SCROLL | SWT.MULTI);
- mResultText.setEditable(true);
- mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
- mResultText.setVisible(false);
- }
-
- // -- End of UI, Start of internal logic ----------
- // Hide everything down-below from SWT designer
- //$hide>>$
-
- private ITask mTask;
-
/**
* Creates a new {@link ProgressTask} with the given title.
* The given task will execute in a separate thread (not the UI thread).
*
* This blocks till the thread ends.
*/
- public static ProgressTask start(Shell parent, String title, ITask task) {
- ProgressTask t = new ProgressTask(parent);
- t.setText(title);
- t.setTask(task);
- t.open();
- return t;
+ public ProgressTask(Shell parent, String title, ITask task) {
+ mDialog = new ProgressDialog(parent, createTaskThread(title, task));
+ mDialog.setText(title);
+ mDialog.open();
}
/**
@@ -153,13 +49,7 @@
* This method can be invoke from a non-UI thread.
*/
public void setDescription(final String descriptionFormat, final Object...args) {
- mDialogShell.getDisplay().asyncExec(new Runnable() {
- public void run() {
- if (!mLabel.isDisposed()) {
- mLabel.setText(String.format(descriptionFormat, args));
- }
- }
- });
+ mDialog.setDescription(descriptionFormat, args);
}
/**
@@ -168,16 +58,7 @@
*/
public void setResult(final String resultFormat, final Object...args) {
mAutomaticallyCloseOnTaskCompletion = false;
- if (!mDialogShell.isDisposed()) {
- mDialogShell.getDisplay().asyncExec(new Runnable() {
- public void run() {
- if (!mResultText.isDisposed()) {
- mResultText.setVisible(true);
- mResultText.setText(String.format(resultFormat, args));
- }
- }
- });
- }
+ mDialog.setResult(resultFormat, args);
}
/**
@@ -187,15 +68,7 @@
* @see ProgressBar#setMaximum(int)
*/
public void setProgressMax(final int max) {
- if (!mDialogShell.isDisposed()) {
- mDialogShell.getDisplay().asyncExec(new Runnable() {
- public void run() {
- if (!mProgressBar.isDisposed()) {
- mProgressBar.setMaximum(max);
- }
- }
- });
- }
+ mDialog.setProgressMax(max);
}
/**
@@ -204,15 +77,7 @@
* This method can be invoked from a non-UI thread.
*/
public void incProgress(final int delta) {
- if (!mDialogShell.isDisposed()) {
- mDialogShell.getDisplay().asyncExec(new Runnable() {
- public void run() {
- if (!mProgressBar.isDisposed()) {
- mProgressBar.setSelection(mProgressBar.getSelection() + delta);
- }
- }
- });
- }
+ mDialog.incProgress(delta);
}
/**
@@ -220,32 +85,28 @@
* It is up to the task thread to pool this and exit.
*/
public boolean cancelRequested() {
- return mCancelRequested;
- }
-
- /** Sets the task that will execute in a separate thread. */
- private void setTask(ITask task) {
- mTask = task;
+ return mDialog.isCancelRequested();
}
/**
- * Starts the task from {@link #setTask(ITask)} in a separate thread.
- * When the task completes, set {@link #mCloseRequested} to end the dialog loop.
+ * Creates a thread to run the task. The thread has not been started yet.
+ * When the task completes, requests to close the dialog.
+ * @return A new thread that will run the task. The thread has not been started yet.
*/
- private void startTask() {
- if (mTask != null) {
- new Thread(getText()) {
+ private Thread createTaskThread(String title, final ITask task) {
+ if (task != null) {
+ return new Thread(title) {
@Override
public void run() {
- mTask.run(ProgressTask.this);
+ task.run(ProgressTask.this);
if (mAutomaticallyCloseOnTaskCompletion) {
- mCloseRequested = true;
+ mDialog.setAutoCloseRequested();
+ } else {
+ mDialog.setManualCloseRequested();
}
}
- }.start();
+ };
}
+ return null;
}
-
- // End of hiding from SWT Designer
- //$hide<<$
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java
index ceb701e..08f5485 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTaskFactory.java
@@ -34,6 +34,6 @@
}
public void start(String title, ITask task) {
- ProgressTask.start(mShell, title, task);
+ new ProgressTask(mShell, title, task);
}
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
index 70641fe..a7223b5 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
@@ -350,7 +350,7 @@
if (!archive.isCompatible()) {
monitor.setResult("Skipping incompatible archive: %1$s",
- archive.getShortDescription());
+ archive.getParentPackage().getShortDescription());
monitor.incProgress(NUM_FETCH_URL_MONITOR_INC + 10);
continue;
}
@@ -360,6 +360,8 @@
archiveFile = downloadArchive(archive, monitor);
if (archiveFile != null) {
if (installArchive(archive, archiveFile, monitor)) {
+ monitor.setResult("Installed: %1$s",
+ archive.getParentPackage().getShortDescription());
num_installed++;
}
}