blob: bec00f8c45c6f268407b0a63aaf2db97aea2ca10 [file] [log] [blame]
/*
* 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.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.repository.AddonPackage;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskFactory;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.LocalSdkParser;
import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdklib.internal.repository.ToolPackage;
import com.android.sdklib.internal.repository.Package.UpdateInfo;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Data shared between {@link UpdaterWindowImpl} and its pages.
*/
class UpdaterData {
private String mOsSdkRoot;
private final ISdkLog mSdkLog;
private ITaskFactory mTaskFactory;
private boolean mUserCanChangeSdkRoot;
private SdkManager mSdkManager;
private AvdManager mAvdManager;
private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
private final RepoSources mSources = new RepoSources();
private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);
private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);
private ImageFactory mImageFactory;
private final SettingsController mSettingsController = new SettingsController();
private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();
private Shell mWindowShell;
public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {
mOsSdkRoot = osSdkRoot;
mSdkLog = sdkLog;
initSdk();
}
// ----- getters, setters ----
public void setOsSdkRoot(String osSdkRoot) {
if (mOsSdkRoot == null || mOsSdkRoot.equals(osSdkRoot) == false) {
mOsSdkRoot = osSdkRoot;
initSdk();
}
}
public String getOsSdkRoot() {
return mOsSdkRoot;
}
public void setTaskFactory(ITaskFactory taskFactory) {
mTaskFactory = taskFactory;
}
public ITaskFactory getTaskFactory() {
return mTaskFactory;
}
public void setUserCanChangeSdkRoot(boolean userCanChangeSdkRoot) {
mUserCanChangeSdkRoot = userCanChangeSdkRoot;
}
public boolean canUserChangeSdkRoot() {
return mUserCanChangeSdkRoot;
}
public RepoSources getSources() {
return mSources;
}
public RepoSourcesAdapter getSourcesAdapter() {
return mSourcesAdapter;
}
public LocalSdkParser getLocalSdkParser() {
return mLocalSdkParser;
}
public LocalSdkAdapter getLocalSdkAdapter() {
return mLocalSdkAdapter;
}
public ISdkLog getSdkLog() {
return mSdkLog;
}
public void setImageFactory(ImageFactory imageFactory) {
mImageFactory = imageFactory;
}
public ImageFactory getImageFactory() {
return mImageFactory;
}
public SdkManager getSdkManager() {
return mSdkManager;
}
public AvdManager getAvdManager() {
return mAvdManager;
}
public SettingsController getSettingsController() {
return mSettingsController;
}
public void addListeners(ISdkListener listener) {
if (mListeners.contains(listener) == false) {
mListeners.add(listener);
}
}
public void removeListener(ISdkListener listener) {
mListeners.remove(listener);
}
public void setWindowShell(Shell windowShell) {
mWindowShell = windowShell;
}
public Shell getWindowShell() {
return mWindowShell;
}
// -----
/**
* Initializes the {@link SdkManager} and the {@link AvdManager}.
*/
private void initSdk() {
mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
try {
mAvdManager = null; // remove the old one if needed.
mAvdManager = new AvdManager(mSdkManager, mSdkLog);
} catch (AndroidLocationException e) {
mSdkLog.error(e, "Unable to read AVDs");
}
// notify adapters/parsers
// TODO
// notify listeners.
notifyListeners();
}
/**
* Reloads the SDK content (targets).
* <p/> This also reloads the AVDs in case their status changed.
* <p/>This does not notify the listeners ({@link ISdkListener}).
*/
public void reloadSdk() {
// reload SDK
mSdkManager.reloadSdk(mSdkLog);
// reload AVDs
if (mAvdManager != null) {
try {
mAvdManager.reloadAvds(mSdkLog);
} catch (AndroidLocationException e) {
// FIXME
}
}
// notify adapters?
mLocalSdkParser.clearPackages();
// TODO
// notify listeners
notifyListeners();
}
/**
* Reloads the AVDs.
* <p/>This does not notify the listeners.
*/
public void reloadAvds() {
// reload AVDs
if (mAvdManager != null) {
try {
mAvdManager.reloadAvds(mSdkLog);
} catch (AndroidLocationException e) {
mSdkLog.error(e, null);
}
}
}
/**
* Returns the list of installed packages, parsing them if this has not yet been done.
*/
public Package[] getInstalledPackage() {
LocalSdkParser parser = getLocalSdkParser();
Package[] packages = parser.getPackages();
if (packages == null) {
// load on demand the first time
packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), getSdkLog());
}
return packages;
}
/**
* Notify the listeners ({@link ISdkListener}) that the SDK was reloaded.
* <p/>This can be called from any thread.
*/
public void notifyListeners() {
if (mWindowShell != null && mListeners.size() > 0) {
mWindowShell.getDisplay().syncExec(new Runnable() {
public void run() {
for (ISdkListener listener : mListeners) {
try {
listener.onSdkChange();
} catch (Throwable t) {
mSdkLog.error(t, null);
}
}
}
});
}
}
/**
* Install the list of given {@link Archive}s. This is invoked by the user selecting some
* packages in the remote page and then clicking "install selected".
*
* @param archives The archives to install. Incompatible ones will be skipped.
*/
public void installArchives(final Collection<Archive> archives) {
if (mTaskFactory == null) {
throw new IllegalArgumentException("Task Factory is null");
}
final boolean forceHttp = getSettingsController().getForceHttp();
mTaskFactory.start("Installing Archives", new ITask() {
public void run(ITaskMonitor monitor) {
final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
monitor.setProgressMax(archives.size() * progressPerArchive);
monitor.setDescription("Preparing to install archives");
boolean installedAddon = false;
boolean installedTools = false;
int numInstalled = 0;
for (Archive archive : archives) {
int nextProgress = monitor.getProgress() + progressPerArchive;
try {
if (monitor.isCancelRequested()) {
break;
}
if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
numInstalled++;
// Check if we successfully installed a tool or add-on package.
if (archive.getParentPackage() instanceof AddonPackage) {
installedAddon = true;
} else if (archive.getParentPackage() instanceof ToolPackage) {
installedTools = true;
}
}
} catch (Throwable t) {
// Display anything unexpected in the monitor.
String msg = t.getMessage();
if (msg != null) {
monitor.setResult("Unexpected Error installing '%1$s': %2$s",
archive.getParentPackage().getShortDescription(), msg);
} else {
// no error info? get the stack call to display it
// At least that'll give us a better bug report.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(baos));
// and display it
monitor.setResult("Unexpected Error installing '%1$s'\n%2$s",
archive.getParentPackage().getShortDescription(),
baos.toString());
}
} finally {
// Always move the progress bar to the desired position.
// This allows internal methods to not have to care in case
// they abort early
monitor.incProgress(nextProgress - monitor.getProgress());
}
}
if (installedAddon) {
// Update the USB vendor ids for adb
try {
mSdkManager.updateAdb();
monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons.");
} catch (Exception e) {
mSdkLog.error(e, "Update ADB failed");
monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons.");
}
}
if (installedAddon || installedTools) {
// We need to restart ADB. Actually since we don't know if it's even
// running, maybe we should just kill it and not start it.
// Note: it turns out even under Windows we don't need to kill adb
// before updating the tools folder, as adb.exe is (surprisingly) not
// locked.
askForAdbRestart(monitor);
}
if (installedTools) {
notifyToolsNeedsToBeRestarted();
}
if (numInstalled == 0) {
monitor.setDescription("Done. Nothing was installed.");
} else {
monitor.setDescription("Done. %1$d %2$s installed.",
numInstalled,
numInstalled == 1 ? "package" : "packages");
//notify listeners something was installed, so that they can refresh
reloadSdk();
}
}
});
}
/**
* Attemps to restart ADB.
*
* If the "ask before restart" setting is set (the default), prompt the user whether
* now is a good time to restart ADB.
* @param monitor
*/
private void askForAdbRestart(ITaskMonitor monitor) {
final boolean[] canRestart = new boolean[] { true };
if (getSettingsController().getAskBeforeAdbRestart()) {
// need to ask for permission first
Display display = mWindowShell.getDisplay();
display.syncExec(new Runnable() {
public void run() {
canRestart[0] = MessageDialog.openQuestion(mWindowShell,
"ADB Restart",
"A package that depends on ADB has been updated. It is recommended " +
"to restart ADB. Is it OK to do it now? If not, you can restart it " +
"manually later.");
}
});
}
if (canRestart[0]) {
AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
adb.stopAdb();
adb.startAdb();
}
}
private void notifyToolsNeedsToBeRestarted() {
Display display = mWindowShell.getDisplay();
display.syncExec(new Runnable() {
public void run() {
MessageDialog.openInformation(mWindowShell,
"Android Tools Updated",
"The Android SDK tool that you are currently using has been updated. " +
"It is recommended that you now close the Android SDK window and re-open it. " +
"If you started this window from Eclipse, please check if the Android " +
"plug-in needs to be updated.");
}
});
}
/**
* Tries to update all the *existing* local packages.
* This first refreshes all sources, then compares the available remote packages with
* the current local ones and suggest updates to be done to the user. Finally all
* selected updates are installed.
*
* @param selectedArchives The list of remote archive to consider for the update.
* This can be null, in which case a list of remote archive is fetched from all
* available sources.
*/
public void updateOrInstallAll(Collection<Archive> selectedArchives) {
if (selectedArchives == null) {
refreshSources(true);
}
final Map<Archive, Archive> updates = findUpdates(selectedArchives);
if (selectedArchives != null) {
// Not only we want to perform updates but we also want to install the
// selected archives. If they do not match an update, list them anyway
// except they map themselves to null (no "old" archive)
for (Archive a : selectedArchives) {
if (!updates.containsKey(a)) {
updates.put(a, null);
}
}
}
UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, updates);
dialog.open();
Collection<Archive> result = dialog.getResult();
if (result != null && result.size() > 0) {
installArchives(result);
}
}
/**
* Refresh all sources. This is invoked either internally (reusing an existing monitor)
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is
* null and a new task should be created.)
*
* @param forceFetching When true, load sources that haven't been loaded yet.
* When false, only refresh sources that have been loaded yet.
*/
public void refreshSources(final boolean forceFetching) {
assert mTaskFactory != null;
final boolean forceHttp = getSettingsController().getForceHttp();
mTaskFactory.start("Refresh Sources",new ITask() {
public void run(ITaskMonitor monitor) {
RepoSource[] sources = mSources.getSources();
monitor.setProgressMax(sources.length);
for (RepoSource source : sources) {
if (forceFetching ||
source.getPackages() != null ||
source.getFetchError() != null) {
source.load(monitor.createSubMonitor(1), forceHttp);
}
monitor.incProgress(1);
}
}
});
}
/**
* Check the local archives vs the remote available packages to find potential updates.
* Return a map [remote archive => local archive] of suitable update candidates.
* Returns null if there's an unexpected error. Otherwise returns a map that can be
* empty but not null.
*
* @param selectedArchives The list of remote archive to consider for the update.
* This can be null, in which case a list of remote archive is fetched from all
* available sources.
*/
private Map<Archive, Archive> findUpdates(Collection<Archive> selectedArchives) {
// Map [remote archive => local archive] of suitable update candidates
Map<Archive, Archive> result = new HashMap<Archive, Archive>();
// First go thru all sources and make a list of all available remote archives
// sorted by package class.
HashMap<Class<? extends Package>, ArrayList<Archive>> availablePkgs =
new HashMap<Class<? extends Package>, ArrayList<Archive>>();
if (selectedArchives != null) {
// Only consider the archives given
for (Archive a : selectedArchives) {
// Only add compatible archives
if (a.isCompatible()) {
Class<? extends Package> clazz = a.getParentPackage().getClass();
ArrayList<Archive> list = availablePkgs.get(clazz);
if (list == null) {
availablePkgs.put(clazz, list = new ArrayList<Archive>());
}
list.add(a);
}
}
} else {
// Get all the available archives from all loaded sources
RepoSource[] remoteSources = getSources().getSources();
for (RepoSource remoteSrc : remoteSources) {
Package[] remotePkgs = remoteSrc.getPackages();
if (remotePkgs != null) {
for (Package remotePkg : remotePkgs) {
Class<? extends Package> clazz = remotePkg.getClass();
ArrayList<Archive> list = availablePkgs.get(clazz);
if (list == null) {
availablePkgs.put(clazz, list = new ArrayList<Archive>());
}
for (Archive a : remotePkg.getArchives()) {
// Only add compatible archives
if (a.isCompatible()) {
list.add(a);
}
}
}
}
}
}
Package[] localPkgs = getLocalSdkParser().getPackages();
if (localPkgs == null) {
// This is unexpected. The local sdk parser should have been called first.
return null;
}
for (Package localPkg : localPkgs) {
// get the available archive list for this package type
ArrayList<Archive> list = availablePkgs.get(localPkg.getClass());
// if this list is empty, we'll never find anything that matches
if (list == null || list.size() == 0) {
continue;
}
// local packages should have one archive at most
Archive[] localArchives = localPkg.getArchives();
if (localArchives != null && localArchives.length > 0) {
Archive localArchive = localArchives[0];
// only consider archives compatible with the current platform
if (localArchive != null && localArchive.isCompatible()) {
// We checked all this archive stuff because that's what eventually gets
// installed, but the "update" mechanism really works on packages. So now
// the real question: is there a remote package that can update this
// local package?
for (Archive availArchive : list) {
UpdateInfo info = localPkg.canBeUpdatedBy(availArchive.getParentPackage());
if (info == UpdateInfo.UPDATE) {
// Found one!
result.put(availArchive, localArchive);
break;
}
}
}
}
}
return result;
}
}