| /* |
| * Copyright (C) 2011 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.sdklib.internal.repository.updater; |
| |
| import com.android.annotations.NonNull; |
| import com.android.sdklib.internal.repository.AddonsListFetcher; |
| import com.android.sdklib.internal.repository.AddonsListFetcher.Site; |
| import com.android.sdklib.internal.repository.DownloadCache; |
| import com.android.sdklib.internal.repository.ITask; |
| import com.android.sdklib.internal.repository.ITaskMonitor; |
| import com.android.sdklib.internal.repository.NullTaskMonitor; |
| import com.android.sdklib.internal.repository.archives.Archive; |
| import com.android.sdklib.internal.repository.packages.Package; |
| import com.android.sdklib.internal.repository.packages.Package.UpdateInfo; |
| import com.android.sdklib.internal.repository.sources.SdkAddonSource; |
| import com.android.sdklib.internal.repository.sources.SdkSource; |
| import com.android.sdklib.internal.repository.sources.SdkSourceCategory; |
| import com.android.sdklib.internal.repository.sources.SdkSources; |
| import com.android.sdklib.internal.repository.sources.SdkSysImgSource; |
| import com.android.sdklib.repository.SdkAddonsListConstants; |
| import com.android.sdklib.repository.SdkRepoConstants; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Loads packages fetched from the remote SDK Repository and keeps track |
| * of their state compared with the current local SDK installation. |
| * |
| * @deprecated |
| * com.android.sdklib.internal.repository has moved into Studio as |
| * com.android.tools.idea.sdk.remote.internal. |
| */ |
| @Deprecated |
| public class PackageLoader { |
| |
| /** The update data context. Never null. */ |
| private final UpdaterData mUpdaterData; |
| |
| /** |
| * The {@link DownloadCache} override. Can be null, in which case the one from |
| * {@link UpdaterData} is used instead. |
| * @see #getDownloadCache() |
| */ |
| private final DownloadCache mOverrideCache; |
| |
| /** |
| * 0 = need to fetch remote addons list once.. |
| * 1 = fetch succeeded, don't need to do it any more. |
| * -1= fetch failed, do it again only if the user requests a refresh |
| * or changes the force-http setting. |
| */ |
| private int mStateFetchRemoteAddonsList; |
| |
| |
| /** |
| * Interface for the callback called by |
| * {@link PackageLoader#loadPackages(boolean, ISourceLoadedCallback)}. |
| * <p/> |
| * After processing each source, the package loader calls {@link #onUpdateSource} |
| * with the list of packages found in that source. |
| * By returning true from {@link #onUpdateSource}, the client tells the loader to |
| * continue and process the next source. By returning false, it tells to stop loading. |
| * <p/> |
| * The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no |
| * matter how the loader stopped, so that the client can clean up or perform any |
| * final action. |
| */ |
| public interface ISourceLoadedCallback { |
| /** |
| * After processing each source, the package loader calls this method with the |
| * list of packages found in that source. |
| * By returning true from {@link #onUpdateSource}, the client tells |
| * the loader to continue and process the next source. |
| * By returning false, it tells to stop loading. |
| * <p/> |
| * <em>Important</em>: This method is called from a sub-thread, so clients which |
| * try to access any UI widgets must wrap their calls into |
| * {@code Display.syncExec(Runnable)} or {@code Display.asyncExec(Runnable)}. |
| * |
| * @param packages All the packages loaded from the source. Never null. |
| * @return True if the load operation should continue, false if it should stop. |
| */ |
| boolean onUpdateSource(SdkSource source, Package[] packages); |
| |
| /** |
| * This method is guaranteed to be called at the end, no matter how the |
| * loader stopped, so that the client can clean up or perform any final action. |
| */ |
| void onLoadCompleted(); |
| } |
| |
| /** |
| * Interface describing the task of installing a specific package. |
| * For details on the operation, |
| * see {@link PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)}. |
| * |
| * @see PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask) |
| */ |
| public interface IAutoInstallTask { |
| /** |
| * Invoked by the loader once a source has been loaded and its package |
| * definitions are known. The method should return the {@code packages} |
| * array and can modify it if necessary. |
| * The loader will call {@link #acceptPackage(Package)} on all the packages returned. |
| * |
| * @param source The source of the packages. Null for the locally installed packages. |
| * @param packages The packages found in the source. |
| */ |
| Package[] filterLoadedSource(SdkSource source, Package[] packages); |
| |
| /** |
| * Called by the install task for every package available (new ones, updates as well |
| * as existing ones that don't have a potential update.) |
| * The method should return true if this is a package that should be installed. |
| * <p/> |
| * <em>Important</em>: This method is called from a sub-thread, so clients who try |
| * to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)} |
| * or {@code Display.asyncExec(Runnable)}. |
| */ |
| boolean acceptPackage(Package pkg); |
| |
| /** |
| * Called when the accepted package has been installed, successfully or not. |
| * If an already installed (aka existing) package has been accepted, this will |
| * be called with a 'true' success and the actual install paths. |
| * <p/> |
| * <em>Important</em>: This method is called from a sub-thread, so clients who try |
| * to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)} |
| * or {@code Display.asyncExec(Runnable)}. |
| */ |
| void setResult(boolean success, Map<Package, File> installPaths); |
| |
| /** |
| * Called when the task is done iterating and completed. |
| */ |
| void taskCompleted(); |
| } |
| |
| /** |
| * Creates a new PackageManager associated with the given {@link UpdaterData} |
| * and using the {@link UpdaterData}'s default {@link DownloadCache}. |
| * |
| * @param updaterData The {@link UpdaterData}. Must not be null. |
| */ |
| public PackageLoader(UpdaterData updaterData) { |
| mUpdaterData = updaterData; |
| mOverrideCache = null; |
| } |
| |
| /** |
| * Creates a new PackageManager associated with the given {@link UpdaterData} |
| * but using the specified {@link DownloadCache} instead of the one from |
| * {@link UpdaterData}. |
| * |
| * @param updaterData The {@link UpdaterData}. Must not be null. |
| * @param cache The {@link DownloadCache} to use instead of the one from {@link UpdaterData}. |
| */ |
| public PackageLoader(UpdaterData updaterData, DownloadCache cache) { |
| mUpdaterData = updaterData; |
| mOverrideCache = cache; |
| } |
| |
| public UpdaterData getUpdaterData() { |
| return mUpdaterData; |
| } |
| |
| /** |
| * Runs a runnable on the UI thread. |
| * The base implementation just runs the runnable right away. |
| * |
| * @param r Non-null runnable. |
| */ |
| protected void runOnUiThread(@NonNull Runnable r) { |
| r.run(); |
| } |
| |
| /** |
| * Loads all packages from the remote repository. |
| * This runs in an {@link ITask}. The call is blocking. |
| * <p/> |
| * The callback is called with each set of {@link PkgItem} found in each source. |
| * The caller is responsible to accumulate the packages given to the callback |
| * after each source is finished loaded. In return the callback tells the loader |
| * whether to continue loading sources. |
| * <p/> |
| * Normally this method doesn't access the remote source if it's already |
| * been loaded in the in-memory source (e.g. don't fetch twice). |
| * |
| * @param overrideExisting Set this to true when the caller wants to |
| * check for updates and discard any existing source already |
| * loaded in memory. It should be false for normal use. |
| * @param sourceLoadedCallback The callback to invoke for each loaded source. |
| */ |
| public void loadPackages( |
| final boolean overrideExisting, |
| final ISourceLoadedCallback sourceLoadedCallback) { |
| try { |
| if (mUpdaterData == null) { |
| return; |
| } |
| |
| mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() { |
| @Override |
| public void run(ITaskMonitor monitor) { |
| monitor.setProgressMax(10); |
| |
| // get local packages and offer them to the callback |
| Package[] localPkgs = |
| mUpdaterData.getInstalledPackages(monitor.createSubMonitor(1)); |
| if (localPkgs == null) { |
| localPkgs = new Package[0]; |
| } |
| if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) { |
| return; |
| } |
| |
| // get remote packages |
| boolean forceHttp = |
| mUpdaterData.getSettingsController().getSettings().getForceHttp(); |
| loadRemoteAddonsList(monitor.createSubMonitor(1)); |
| |
| SdkSource[] sources = mUpdaterData.getSources().getAllSources(); |
| try { |
| if (sources != null && sources.length > 0) { |
| ITaskMonitor subMonitor = monitor.createSubMonitor(8); |
| subMonitor.setProgressMax(sources.length); |
| for (SdkSource source : sources) { |
| Package[] pkgs = source.getPackages(); |
| if (pkgs == null || overrideExisting) { |
| source.load(getDownloadCache(), |
| subMonitor.createSubMonitor(1), |
| forceHttp); |
| pkgs = source.getPackages(); |
| } |
| if (pkgs == null) { |
| continue; |
| } |
| |
| // Notify the callback a new source has finished loading. |
| // If the callback requests so, stop right away. |
| if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) { |
| return; |
| } |
| } |
| } |
| } catch(Exception e) { |
| monitor.logError("Loading source failed: %1$s", e.toString()); |
| } finally { |
| monitor.setDescription("Done loading packages."); |
| } |
| } |
| }); |
| } finally { |
| sourceLoadedCallback.onLoadCompleted(); |
| } |
| } |
| |
| /** |
| * Load packages, source by source using |
| * {@link #loadPackages(boolean, ISourceLoadedCallback)}, |
| * and executes the given {@link IAutoInstallTask} on the current package list. |
| * That is for each package known, the install task is queried to find if |
| * the package is the one to be installed or updated. |
| * <p/> |
| * - If an already installed package is accepted by the task, it is returned. <br/> |
| * - If a new package (remotely available but not installed locally) is accepted, |
| * the user will be <em>prompted</em> for permission to install it. <br/> |
| * - If an existing package has updates, the install task will be accept if it |
| * accepts one of the updating packages, and if yes the the user will be |
| * <em>prompted</em> for permission to install it. <br/> |
| * <p/> |
| * Only one package can be accepted, after which the task is completed. |
| * There is no direct return value, {@link IAutoInstallTask#setResult} is called on the |
| * result of the accepted package. |
| * When the task is completed, {@link IAutoInstallTask#taskCompleted()} is called. |
| * <p/> |
| * The call is blocking. Although the name says "Task", this is not an {@link ITask} |
| * running in its own thread but merely a synchronous call. |
| * |
| * @param installFlags Flags for installation such as |
| * {@link UpdaterData#TOOLS_MSG_UPDATED_FROM_ADT}. |
| * @param installTask The task to perform. |
| */ |
| public void loadPackagesWithInstallTask( |
| final int installFlags, |
| final IAutoInstallTask installTask) { |
| |
| loadPackages(false /*overrideExisting*/, new ISourceLoadedCallback() { |
| List<Archive> mArchivesToInstall = new ArrayList<Archive>(); |
| Map<Package, File> mInstallPaths = new HashMap<Package, File>(); |
| |
| @Override |
| public boolean onUpdateSource(SdkSource source, Package[] packages) { |
| packages = installTask.filterLoadedSource(source, packages); |
| if (packages == null || packages.length == 0) { |
| // Tell loadPackages() to process the next source. |
| return true; |
| } |
| |
| for (Package pkg : packages) { |
| if (pkg.isLocal()) { |
| // This is a local (aka installed) package |
| if (installTask.acceptPackage(pkg)) { |
| // If the caller is accepting an installed package, |
| // return a success and give the package's install path |
| Archive[] a = pkg.getArchives(); |
| // an installed package should have one local compatible archive |
| if (a.length == 1 && a[0].isCompatible()) { |
| mInstallPaths.put(pkg, new File(a[0].getLocalOsPath())); |
| } |
| } |
| |
| } else { |
| // This is a remote package |
| if (installTask.acceptPackage(pkg)) { |
| // The caller is accepting this remote package. We'll install it. |
| for (Archive archive : pkg.getArchives()) { |
| if (archive.isCompatible()) { |
| mArchivesToInstall.add(archive); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| // Tell loadPackages() to process the next source. |
| return true; |
| } |
| |
| @Override |
| public void onLoadCompleted() { |
| if (!mArchivesToInstall.isEmpty()) { |
| installArchives(mArchivesToInstall); |
| } |
| if (mInstallPaths == null) { |
| installTask.setResult(false, null); |
| } else { |
| installTask.setResult(true, mInstallPaths); |
| } |
| |
| installTask.taskCompleted(); |
| } |
| |
| /** |
| * Shows the UI of the install selector. |
| * If the package is then actually installed, refresh the local list and |
| * notify the install task of the installation path. |
| * |
| * @param archivesToInstall The archives to install. |
| */ |
| private void installArchives(final List<Archive> archivesToInstall) { |
| // Actually install the new archives that we just found. |
| // This will display some UI so we need a shell's sync exec. |
| |
| final List<Archive> installedArchives = new ArrayList<Archive>(); |
| |
| runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| List<Archive> archives = |
| mUpdaterData.updateOrInstallAll_WithGUI( |
| archivesToInstall, |
| true /* includeObsoletes */, |
| installFlags); |
| |
| if (archives != null) { |
| installedArchives.addAll(archives); |
| } |
| } |
| }); |
| |
| if (installedArchives.isEmpty()) { |
| // We failed to install anything. |
| mInstallPaths = null; |
| return; |
| } |
| |
| // The local package list has changed, make sure to refresh it |
| mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog()); |
| mUpdaterData.getLocalSdkParser().clearPackages(); |
| final Package[] localPkgs = mUpdaterData.getInstalledPackages( |
| new NullTaskMonitor(mUpdaterData.getSdkLog())); |
| |
| // Scan the installed package list to find the install paths. |
| for (Archive installedArchive : installedArchives) { |
| Package pkg = installedArchive.getParentPackage(); |
| |
| for (Package localPkg : localPkgs) { |
| if (localPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) { |
| Archive[] localArchive = localPkg.getArchives(); |
| if (localArchive.length == 1 && localArchive[0].isCompatible()) { |
| mInstallPaths.put( |
| localPkg, |
| new File(localArchive[0].getLocalOsPath())); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| /** |
| * Loads the remote add-ons list. |
| */ |
| public void loadRemoteAddonsList(ITaskMonitor monitor) { |
| |
| if (mStateFetchRemoteAddonsList != 0) { |
| return; |
| } |
| |
| mUpdaterData.getTaskFactory().start("Load Add-ons List", monitor, new ITask() { |
| @Override |
| public void run(ITaskMonitor subMonitor) { |
| loadRemoteAddonsListInTask(subMonitor); |
| } |
| }); |
| } |
| |
| private void loadRemoteAddonsListInTask(ITaskMonitor monitor) { |
| mStateFetchRemoteAddonsList = -1; |
| |
| String url = SdkAddonsListConstants.URL_ADDON_LIST; |
| |
| // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined |
| String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$ |
| if (baseUrl != null) { |
| if (baseUrl.length() > 0 && baseUrl.endsWith("/")) { //$NON-NLS-1$ |
| if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) { |
| url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length()); |
| } |
| } else { |
| monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$ |
| } |
| } |
| |
| if (mUpdaterData.getSettingsController().getSettings().getForceHttp()) { |
| url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| // Hook to bypass loading 3rd party addons lists. |
| boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null; |
| |
| AddonsListFetcher fetcher = new AddonsListFetcher(); |
| Site[] sites = fetcher.fetch(url, getDownloadCache(), monitor); |
| if (sites != null) { |
| SdkSources sources = mUpdaterData.getSources(); |
| sources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY); |
| |
| if (fetch3rdParties) { |
| for (Site s : sites) { |
| switch (s.getType()) { |
| case ADDON_SITE: |
| sources.add(SdkSourceCategory.ADDONS_3RD_PARTY, |
| new SdkAddonSource(s.getUrl(), s.getUiName())); |
| break; |
| case SYS_IMG_SITE: |
| sources.add(SdkSourceCategory.ADDONS_3RD_PARTY, |
| new SdkSysImgSource(s.getUrl(), s.getUiName())); |
| break; |
| } |
| } |
| } |
| |
| sources.notifyChangeListeners(); |
| |
| mStateFetchRemoteAddonsList = 1; |
| } |
| |
| monitor.setDescription("Fetched Add-ons List successfully"); |
| } |
| |
| /** |
| * Returns the {@link DownloadCache} to use. |
| * |
| * @return Returns {@link #mOverrideCache} if not null; otherwise returns the |
| * one from {@link UpdaterData} is used instead. |
| */ |
| private DownloadCache getDownloadCache() { |
| return mOverrideCache != null ? mOverrideCache : mUpdaterData.getDownloadCache(); |
| } |
| } |