| /* |
| * Copyright (C) 2008 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; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.annotations.VisibleForTesting; |
| import com.android.annotations.VisibleForTesting.Visibility; |
| import com.android.prefs.AndroidLocation; |
| import com.android.prefs.AndroidLocation.AndroidLocationException; |
| import com.android.sdklib.internal.androidTarget.AddOnTarget; |
| import com.android.sdklib.internal.androidTarget.PlatformTarget; |
| import com.android.sdklib.repository.FullRevision; |
| import com.android.sdklib.repository.descriptors.IPkgDesc; |
| import com.android.sdklib.repository.descriptors.PkgType; |
| import com.android.sdklib.repository.local.LocalExtraPkgInfo; |
| import com.android.sdklib.repository.local.LocalPkgInfo; |
| import com.android.sdklib.repository.local.LocalSdk; |
| import com.android.utils.ILogger; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| /** |
| * The SDK manager parses the SDK folder and gives access to the content. |
| * @see PlatformTarget |
| * @see AddOnTarget |
| */ |
| public class SdkManager { |
| |
| @SuppressWarnings("unused") |
| private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG") != null; //$NON-NLS-1$ |
| |
| /** Preference file containing the usb ids for adb */ |
| private static final String ADB_INI_FILE = "adb_usb.ini"; //$NON-NLS-1$ |
| //0--------90--------90--------90--------90--------90--------90--------90--------9 |
| private static final String ADB_INI_HEADER = |
| "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" + //$NON-NLS-1$ |
| "# USE 'android update adb' TO GENERATE.\n" + //$NON-NLS-1$ |
| "# 1 USB VENDOR ID PER LINE.\n"; //$NON-NLS-1$ |
| |
| /** Embedded reference to the new local SDK object. */ |
| private final LocalSdk mLocalSdk; |
| |
| /** |
| * Create a new {@link SdkManager} instance. |
| * External users should use {@link #createManager(String, ILogger)}. |
| * |
| * @param osSdkPath the location of the SDK. |
| */ |
| @VisibleForTesting(visibility=Visibility.PRIVATE) |
| protected SdkManager(@NonNull String osSdkPath) { |
| mLocalSdk = new LocalSdk(new File(osSdkPath)); |
| } |
| |
| /** |
| * Creates an @{linkplain SdkManager} for an existing @{link LocalSdk}. |
| * |
| * @param localSdk the SDK to use with the SDK manager |
| */ |
| private SdkManager(@NonNull LocalSdk localSdk) { |
| mLocalSdk = localSdk; |
| } |
| |
| /** |
| * Creates an {@link SdkManager} for a given sdk location. |
| * @param osSdkPath the location of the SDK. |
| * @param log the ILogger object receiving warning/error from the parsing. |
| * @return the created {@link SdkManager} or null if the location is not valid. |
| */ |
| @Nullable |
| public static SdkManager createManager( |
| @NonNull String osSdkPath, |
| @NonNull ILogger log) { |
| try { |
| SdkManager manager = new SdkManager(osSdkPath); |
| manager.reloadSdk(log); |
| |
| return manager; |
| } catch (Throwable throwable) { |
| log.error(throwable, "Error parsing the sdk."); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Creates an @{linkplain SdkManager} for an existing @{link LocalSdk}. |
| * |
| * @param localSdk the SDK to use with the SDK manager |
| */ |
| @NonNull |
| public static SdkManager createManager(@NonNull LocalSdk localSdk) { |
| return new SdkManager(localSdk); |
| } |
| |
| @NonNull |
| public LocalSdk getLocalSdk() { |
| return mLocalSdk; |
| } |
| |
| /** |
| * Reloads the content of the SDK. |
| * |
| * @param log the ILogger object receiving warning/error from the parsing. |
| */ |
| public void reloadSdk(@NonNull ILogger log) { |
| mLocalSdk.clearLocalPkg(PkgType.PKG_ALL); |
| } |
| |
| /** |
| * Checks whether any of the SDK platforms/add-ons/build-tools have changed on-disk |
| * since we last loaded the SDK. This does not reload the SDK nor does it |
| * change the underlying targets. |
| * |
| * @return True if at least one directory or source.prop has changed. |
| */ |
| public boolean hasChanged() { |
| return hasChanged(null); |
| } |
| |
| /** |
| * Checks whether any of the SDK platforms/add-ons/build-tools have changed on-disk |
| * since we last loaded the SDK. This does not reload the SDK nor does it |
| * change the underlying targets. |
| * |
| * @param log An optional logger used to print verbose info on what changed. Can be null. |
| * @return True if at least one directory or source.prop has changed. |
| */ |
| public boolean hasChanged(@Nullable ILogger log) { |
| return mLocalSdk.hasChanged(EnumSet.of(PkgType.PKG_PLATFORM, |
| PkgType.PKG_ADDON, |
| PkgType.PKG_BUILD_TOOLS)); |
| } |
| |
| /** |
| * Returns the location of the SDK. |
| */ |
| @NonNull |
| public String getLocation() { |
| File f = mLocalSdk.getLocation(); |
| // Our LocalSdk is created with a file path, so we know the location won't be null. |
| assert f != null; |
| return f.getPath(); |
| } |
| |
| /** |
| * Returns the targets (platforms & addons) that are available in the SDK. |
| * The target list is created on demand the first time then cached. |
| * It will not refreshed unless {@link #reloadSdk(ILogger)} is called. |
| * <p/> |
| * The array can be empty but not null. |
| */ |
| @NonNull |
| public IAndroidTarget[] getTargets() { |
| return mLocalSdk.getTargets(); |
| } |
| |
| /** |
| * Returns an unmodifiable set of known build-tools revisions. Can be empty but not null. |
| * Deprecated. I don't think anything uses this. |
| */ |
| @Deprecated |
| @NonNull |
| public Set<FullRevision> getBuildTools() { |
| LocalPkgInfo[] pkgs = mLocalSdk.getPkgsInfos(PkgType.PKG_BUILD_TOOLS); |
| TreeSet<FullRevision> bt = new TreeSet<FullRevision>(); |
| for (LocalPkgInfo pkg : pkgs) { |
| IPkgDesc d = pkg.getDesc(); |
| if (d.hasFullRevision()) { |
| bt.add(d.getFullRevision()); |
| } |
| } |
| return Collections.unmodifiableSet(bt); |
| } |
| |
| /** |
| * Returns the highest build-tool revision known. Can be null. |
| * |
| * @return The highest build-tool revision known, or null. |
| */ |
| @Nullable |
| public BuildToolInfo getLatestBuildTool() { |
| return mLocalSdk.getLatestBuildTool(); |
| } |
| |
| /** |
| * Returns the {@link BuildToolInfo} for the given revision. |
| * |
| * @param revision The requested revision. |
| * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is |
| * not part of the known set returned by {@link #getBuildTools()}. |
| */ |
| @Nullable |
| public BuildToolInfo getBuildTool(@Nullable FullRevision revision) { |
| return mLocalSdk.getBuildTool(revision); |
| } |
| |
| /** |
| * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}. |
| * |
| * @param hash the {@link IAndroidTarget} hash string. |
| * @return The matching {@link IAndroidTarget} or null. |
| */ |
| @Nullable |
| public IAndroidTarget getTargetFromHashString(@Nullable String hash) { |
| return mLocalSdk.getTargetFromHashString(hash); |
| } |
| |
| /** |
| * Updates adb with the USB devices declared in the SDK add-ons. |
| * @throws AndroidLocationException |
| * @throws IOException |
| */ |
| public void updateAdb() throws AndroidLocationException, IOException { |
| FileWriter writer = null; |
| try { |
| // get the android prefs location to know where to write the file. |
| File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE); |
| writer = new FileWriter(adbIni); |
| |
| // first, put all the vendor id in an HashSet to remove duplicate. |
| HashSet<Integer> set = new HashSet<Integer>(); |
| IAndroidTarget[] targets = getTargets(); |
| for (IAndroidTarget target : targets) { |
| if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) { |
| set.add(target.getUsbVendorId()); |
| } |
| } |
| |
| // write file header. |
| writer.write(ADB_INI_HEADER); |
| |
| // now write the Id in a text file, one per line. |
| for (Integer i : set) { |
| writer.write(String.format("0x%04x\n", i)); //$NON-NLS-1$ |
| } |
| } finally { |
| if (writer != null) { |
| writer.close(); |
| } |
| } |
| } |
| |
| /** |
| * Returns the greatest {@link LayoutlibVersion} found amongst all platform |
| * targets currently loaded in the SDK. |
| * <p/> |
| * We only started recording Layoutlib Versions recently in the platform meta data |
| * so it's possible to have an SDK with many platforms loaded but no layoutlib |
| * version defined. |
| * |
| * @return The greatest {@link LayoutlibVersion} or null if none is found. |
| * @deprecated This does NOT solve the right problem and will be changed later. |
| */ |
| @Deprecated |
| @Nullable |
| public LayoutlibVersion getMaxLayoutlibVersion() { |
| LayoutlibVersion maxVersion = null; |
| |
| for (IAndroidTarget target : getTargets()) { |
| if (target instanceof PlatformTarget) { |
| LayoutlibVersion lv = ((PlatformTarget) target).getLayoutlibVersion(); |
| if (lv != null) { |
| if (maxVersion == null || lv.compareTo(maxVersion) > 0) { |
| maxVersion = lv; |
| } |
| } |
| } |
| } |
| |
| return maxVersion; |
| } |
| |
| /** |
| * Returns a map of the <em>root samples directories</em> located in the SDK/extras packages. |
| * No guarantee is made that the extras' samples directory actually contain any valid samples. |
| * The only guarantee is that the root samples directory actually exists. |
| * The map is { File: Samples root directory => String: Extra package display name. } |
| * |
| * @return A non-null possibly empty map of extra samples directories and their associated |
| * extra package display name. |
| */ |
| @NonNull |
| public Map<File, String> getExtraSamples() { |
| |
| LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA); |
| Map<File, String> samples = new HashMap<File, String>(); |
| |
| for (LocalPkgInfo info : pkgsInfos) { |
| assert info instanceof LocalExtraPkgInfo; |
| |
| File root = info.getLocalDir(); |
| File path = new File(root, SdkConstants.FD_SAMPLES); |
| if (path.isDirectory()) { |
| samples.put(path, info.getListDescription()); |
| continue; |
| } |
| // Some old-style extras simply have a single "sample" directory. |
| // Accept it if it contains an AndroidManifest.xml. |
| path = new File(root, SdkConstants.FD_SAMPLE); |
| if (path.isDirectory() && |
| new File(path, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) { |
| samples.put(path, info.getListDescription()); |
| } |
| } |
| |
| return samples; |
| } |
| |
| /** |
| * Returns a map of all the extras found in the <em>local</em> SDK with their major revision. |
| * <p/> |
| * Map keys are in the form "vendor-id/path-id". These ids uniquely identify an extra package. |
| * The version is the incremental integer major revision of the package. |
| * |
| * @return A non-null possibly empty map of { string "vendor/path" => integer major revision } |
| * @deprecated Starting with add-on schema 6, extras can have full revisions instead of just |
| * major revisions. This API only returns the major revision. Callers should be modified |
| * to use the new {code LocalSdk.getPkgInfo(PkgType.PKG_EXTRAS)} API instead. |
| */ |
| @Deprecated |
| @NonNull |
| public Map<String, Integer> getExtrasVersions() { |
| LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(PkgType.PKG_EXTRA); |
| Map<String, Integer> extraVersions = new TreeMap<String, Integer>(); |
| |
| for (LocalPkgInfo info : pkgsInfos) { |
| assert info instanceof LocalExtraPkgInfo; |
| if (info instanceof LocalExtraPkgInfo) { |
| LocalExtraPkgInfo ei = (LocalExtraPkgInfo) info; |
| IPkgDesc d = ei.getDesc(); |
| String vendor = d.getVendor().getId(); |
| String path = d.getPath(); |
| int majorRev = d.getFullRevision().getMajor(); |
| |
| extraVersions.put(vendor + '/' + path, majorRev); |
| } |
| } |
| |
| return extraVersions; |
| } |
| |
| /** Returns the platform tools version if installed, null otherwise. */ |
| @Nullable |
| public String getPlatformToolsVersion() { |
| LocalPkgInfo info = mLocalSdk.getPkgInfo(PkgType.PKG_PLATFORM_TOOLS); |
| IPkgDesc d = info == null ? null : info.getDesc(); |
| if (d != null && d.hasFullRevision()) { |
| return d.getFullRevision().toShortString(); |
| } |
| |
| return null; |
| } |
| |
| |
| // ------------- |
| |
| public static class LayoutlibVersion implements Comparable<LayoutlibVersion> { |
| private final int mApi; |
| private final int mRevision; |
| |
| public static final int NOT_SPECIFIED = 0; |
| |
| public LayoutlibVersion(int api, int revision) { |
| mApi = api; |
| mRevision = revision; |
| } |
| |
| public int getApi() { |
| return mApi; |
| } |
| |
| public int getRevision() { |
| return mRevision; |
| } |
| |
| @Override |
| public int compareTo(@NonNull LayoutlibVersion rhs) { |
| boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED; |
| int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0); |
| int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0); |
| return lhsValue - rhsValue; |
| } |
| } |
| } |