blob: 02df59ed2943b67c98102d368ce1564621454fcb [file] [log] [blame]
/*
* Copyright (C) 2013 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.repository.remote;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.internal.repository.AddonsListFetcher;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.NullTaskMonitor;
import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.sources.SdkAddonSource;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
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.internal.repository.updater.SettingsController;
import com.android.sdklib.internal.repository.updater.SettingsController.OnChangedListener;
import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.utils.ILogger;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/**
* This class keeps information on the remote SDK repository.
*/
public class RemoteSdk {
/** Default expiration delay is 24 hours. */
public final static long DEFAULT_EXPIRATION_PERIOD_MS = 24 * 3600 * 1000;
private final SettingsController mSettingsController;
private final SdkSources mSdkSources = new SdkSources();
private long mSdkSourceTS;
private DownloadCache mDownloadCache;
public RemoteSdk(SettingsController settingsController) {
mSettingsController = settingsController;
settingsController.registerOnChangedListener(new OnChangedListener() {
@Override
public void onSettingsChanged(@NonNull SettingsController controller,
@NonNull SettingsController.Settings oldSettings) {
// Reset the download cache if it doesn't match the right strategy.
// The cache instance gets lazily recreated later in getDownloadCache().
mDownloadCache = null;
}
});
}
/**
* Fetches the remote list of packages.
* <p/>
* This respects the settings from the {@link SettingsController} which
* dictates whether the {@link DownloadCache} is used and whether HTTP
* is enforced over HTTPS.
* <p/>
* The call may block on network access. Callers will likely want to invoke this
* from a thread and make sure the logger is thread-safe with regard to UI updates.
*
* @param sources The sources to download from.
* @param logger A logger to report status & progress.
* @return A non-null map of {@link PkgType} to {@link RemotePkgInfo}
* describing the remote packages available for install/download.
*/
@NonNull
public Multimap<PkgType, RemotePkgInfo> fetch(@NonNull SdkSources sources,
@NonNull ILogger logger) {
Multimap<PkgType, RemotePkgInfo> remotes = HashMultimap.create();
boolean forceHttp = mSettingsController.getSettings().getForceHttp();
// Implementation detail: right now this reuses the SdkSource(s) classes
// from the sdk-repository v2. The problem with that is that the sources are
// mutable and hold the fetch logic and hold the packages array.
// Instead I'd prefer to have the sources be immutable descriptors and move
// the fetch logic here. Eventually my goal is to get rid of them
// and include the logic directly here instead but for right now lets
// just start with what we have to avoid implementing it all at once.
// It does mean however that this code needs to convert the old Package
// type into the new RemotePkgInfo type.
for (SdkSource source : sources.getAllSources()) {
source.load(getDownloadCache(),
new NullTaskMonitor(logger),
forceHttp);
Package[] pkgs = source.getPackages();
if (pkgs == null || pkgs.length == 0) {
continue;
}
// Adapt the legacy Package instances into the new RemotePkgInfo
for (Package p : pkgs) {
IPkgDesc d = p.getPkgDesc();
RemotePkgInfo r = new RemotePkgInfo(d, source);
remotes.put(d.getType(), r);
}
}
return remotes;
}
/**
* Returns the {@link SdkSources} object listing all sources to load from.
* This includes the main repository.xml, the main addon.xml as well as all the
* add-ons or sys-img xmls listed in the addons-list.xml.
* <p/>
* The method caches the last access and only refresh it if data is either not
* present or the expiration time has be passed.
*
* @param expirationDelayMs The expiration delay in milliseconds.
* Use {@link #DEFAULT_EXPIRATION_PERIOD_MS} by default.
* @param logger A non-null object to log messages. TODO change to an ITaskMonitor
* to be able to update the caller's progress bar UI, if any.
* @return A non-null {@link SdkSources}
*/
@NonNull
public SdkSources fetchSources(long expirationDelayMs, @NonNull ILogger logger) {
long now = System.currentTimeMillis();
boolean expired = (now - mSdkSourceTS) > expirationDelayMs;
// Load the conventional sources.
// For testing, the env var can be set to replace the default root download URL.
// It must end with a / and its the location where the updater will look for
// the repository.xml, addons_list.xml and such files.
if (expired || !mSdkSources.hasSources(SdkSourceCategory.ANDROID_REPO)) {
String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
if (baseUrl == null || baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE;
}
mSdkSources.removeAll(SdkSourceCategory.ANDROID_REPO);
mSdkSources.add(SdkSourceCategory.ANDROID_REPO,
new SdkRepoSource(baseUrl,
SdkSourceCategory.ANDROID_REPO.getUiName()));
}
// Load user sources (this will also notify change listeners but this operation is
// done early enough that there shouldn't be any anyway.)
if (expired || !mSdkSources.hasSources(SdkSourceCategory.USER_ADDONS)) {
mSdkSources.loadUserAddons(logger);
}
if (expired || !mSdkSources.hasSources(SdkSourceCategory.ADDONS_3RD_PARTY)) {
ITaskMonitor tempMonitor = new NullTaskMonitor(logger);
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 {
tempMonitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
}
}
if (mSettingsController.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(), tempMonitor);
if (sites != null) {
mSdkSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
if (fetch3rdParties) {
for (Site s : sites) {
switch (s.getType()) {
case ADDON_SITE:
mSdkSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
new SdkAddonSource(s.getUrl(), s.getUiName()));
break;
case SYS_IMG_SITE:
mSdkSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
new SdkSysImgSource(s.getUrl(), s.getUiName()));
break;
}
}
}
mSdkSources.notifyChangeListeners();
}
}
mSdkSourceTS = now;
return mSdkSources;
}
/**
* Returns the {@link DownloadCache}
* Extracted so that we can override this in unit tests.
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected DownloadCache getDownloadCache() {
if (mDownloadCache == null) {
mDownloadCache = new DownloadCache(
mSettingsController.getSettings().getUseDownloadCache() ?
DownloadCache.Strategy.FRESH_CACHE :
DownloadCache.Strategy.DIRECT);
}
return mDownloadCache;
}
}