diff options
20 files changed, 594 insertions, 533 deletions
diff --git a/android/src/com/android/tools/idea/avdmanager/SystemImageList.java b/android/src/com/android/tools/idea/avdmanager/SystemImageList.java index 5b39ee79af2..05faf7aaa9a 100644 --- a/android/src/com/android/tools/idea/avdmanager/SystemImageList.java +++ b/android/src/com/android/tools/idea/avdmanager/SystemImageList.java @@ -283,7 +283,7 @@ public class SystemImageList extends JPanel implements ListSelectionListener { @Nullable private List<SystemImageDescription> getRemoteImages() { List<SystemImageDescription> items = Lists.newArrayList(); - Set<RemotePkgInfo> infos = mySdkState.getUpdates().getNewPkgs(); + Set<RemotePkgInfo> infos = mySdkState.getPackages().getNewPkgs(); if (infos.isEmpty()) { return null; diff --git a/android/src/com/android/tools/idea/sdk/SdkLifecycleListener.java b/android/src/com/android/tools/idea/sdk/SdkLifecycleListener.java deleted file mode 100755 index ce2d59899f3..00000000000 --- a/android/src/com/android/tools/idea/sdk/SdkLifecycleListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 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.tools.idea.sdk; - -import com.android.annotations.NonNull; -import com.intellij.util.messages.Topic; -import org.jetbrains.android.sdk.AndroidSdkData; - -public interface SdkLifecycleListener { - Topic<SdkLifecycleListener> TOPIC = Topic.create("Android SDK lifecycle notifications", SdkLifecycleListener.class); - - void localSdkLoaded (@NonNull AndroidSdkData sdkData); - void remoteSdkLoaded(@NonNull AndroidSdkData sdkData); - void updatesComputed(@NonNull AndroidSdkData sdkData); - - abstract class Adapter implements SdkLifecycleListener { - @Override - public void localSdkLoaded(@NonNull AndroidSdkData sdkData) {} - - @Override - public void remoteSdkLoaded(@NonNull AndroidSdkData sdkData) {} - - @Override - public void updatesComputed(@NonNull AndroidSdkData sdkData) {} - } -} diff --git a/android/src/com/android/tools/idea/sdk/SdkPackages.java b/android/src/com/android/tools/idea/sdk/SdkPackages.java new file mode 100755 index 00000000000..95e60e88b33 --- /dev/null +++ b/android/src/com/android/tools/idea/sdk/SdkPackages.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2015 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.tools.idea.sdk; + +import com.android.annotations.NonNull; +import com.android.sdklib.repository.descriptors.IPkgDesc; +import com.android.sdklib.repository.descriptors.PkgType; +import com.android.sdklib.repository.local.LocalPkgInfo; +import com.android.tools.idea.sdk.remote.RemotePkgInfo; +import com.android.tools.idea.sdk.remote.UpdatablePkgInfo; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.common.collect.TreeMultimap; + +import java.util.Set; + + +/** + * Store of current local and remote packages, in convenient forms. + */ +public final class SdkPackages { + private final Set<UpdatablePkgInfo> myUpdatedPkgs = Sets.newTreeSet(); + private final Set<RemotePkgInfo> myNewPkgs = Sets.newTreeSet(); + private final long myTimestampMs; + private Set<UpdatablePkgInfo> myConsolidatedPkgs = Sets.newTreeSet(); + private LocalPkgInfo[] myLocalPkgInfos = new LocalPkgInfo[0]; + private Multimap<PkgType, RemotePkgInfo> myRemotePkgInfos = TreeMultimap.create(); + + SdkPackages() { + myTimestampMs = System.currentTimeMillis(); + } + + public SdkPackages(LocalPkgInfo[] localPkgs, Multimap<PkgType, RemotePkgInfo> remotePkgs) { + this(); + setLocalPkgInfos(localPkgs); + setRemotePkgInfos(remotePkgs); + } + + /** + * Returns the timestamp (in {@link System#currentTimeMillis()} time) when this object was created. + */ + public long getTimestampMs() { + return myTimestampMs; + } + + /** + * Returns the set of packages that have local updates available. + * Use {@link LocalPkgInfo#getUpdate()} to retrieve the computed updated candidate. + * + * @return A non-null, possibly empty Set of update candidates. + */ + @NonNull + public Set<UpdatablePkgInfo> getUpdatedPkgs() { + return myUpdatedPkgs; + } + + /** + * Returns the set of new remote packages that are not locally present + * and that the user could install. + * + * @return A non-null, possibly empty Set of new install candidates. + */ + @NonNull + public Set<RemotePkgInfo> getNewPkgs() { + return myNewPkgs; + } + + /** + * Returns a set of {@link UpdatablePackageInfo}s representing all known local and remote packages. Remote packages corresponding + * to local packages will be represented by a single item containing both the local and remote info.. + */ + @NonNull + public Set<UpdatablePkgInfo> getConsolidatedPkgs() { + return myConsolidatedPkgs; + } + + @NonNull + public LocalPkgInfo[] getLocalPkgInfos() { + return myLocalPkgInfos; + } + + public Multimap<PkgType, RemotePkgInfo> getRemotePkgInfos() { + return myRemotePkgInfos; + } + + void setLocalPkgInfos(LocalPkgInfo[] packages) { + myLocalPkgInfos = packages; + computeUpdates(); + } + + void setRemotePkgInfos(Multimap<PkgType, RemotePkgInfo> packages) { + myRemotePkgInfos = packages; + computeUpdates(); + } + + private void computeUpdates() { + Set<UpdatablePkgInfo> newConsolidatedPkgs = Sets.newTreeSet(); + UpdatablePkgInfo[] updatablePkgInfos = new UpdatablePkgInfo[myLocalPkgInfos.length]; + for (int i = 0; i < myLocalPkgInfos.length; i++) { + updatablePkgInfos[i] = new UpdatablePkgInfo(myLocalPkgInfos[i]); + } + Set<RemotePkgInfo> updates = Sets.newTreeSet(); + + // Find updates to locally installed packages + for (UpdatablePkgInfo info : updatablePkgInfos) { + RemotePkgInfo update = findUpdate(info); + if (update != null) { + info.setRemote(update); + myUpdatedPkgs.add(info); + updates.add(update); + } + newConsolidatedPkgs.add(info); + } + + // Find new packages not yet installed + nextRemote: for (RemotePkgInfo remote : myRemotePkgInfos.values()) { + if (updates.contains(remote)) { + // if package is already a known update, it's not new. + continue nextRemote; + } + IPkgDesc remoteDesc = remote.getPkgDesc(); + for (UpdatablePkgInfo info : updatablePkgInfos) { + IPkgDesc localDesc = info.getLocalInfo().getDesc(); + if (remoteDesc.compareTo(localDesc) == 0 || remoteDesc.isUpdateFor(localDesc)) { + // if package is same as an installed or is an update for an installed + // one, then it's not new. + continue nextRemote; + } + } + + myNewPkgs.add(remote); + newConsolidatedPkgs.add(new UpdatablePkgInfo(remote)); + } + myConsolidatedPkgs = newConsolidatedPkgs; + } + + private RemotePkgInfo findUpdate(@NonNull UpdatablePkgInfo info) { + RemotePkgInfo currUpdatePkg = null; + IPkgDesc currUpdateDesc = null; + IPkgDesc localDesc = info.getLocalInfo().getDesc(); + + for (RemotePkgInfo remote: myRemotePkgInfos.get(localDesc.getType())) { + IPkgDesc remoteDesc = remote.getPkgDesc(); + if ((currUpdateDesc == null && remoteDesc.isUpdateFor(localDesc)) || + (currUpdateDesc != null && remoteDesc.isUpdateFor(currUpdateDesc))) { + currUpdatePkg = remote; + currUpdateDesc = remoteDesc; + } + } + + return currUpdatePkg; + } +} diff --git a/android/src/com/android/tools/idea/sdk/SdkState.java b/android/src/com/android/tools/idea/sdk/SdkState.java index d232b75a22b..2927d86af9a 100755 --- a/android/src/com/android/tools/idea/sdk/SdkState.java +++ b/android/src/com/android/tools/idea/sdk/SdkState.java @@ -19,28 +19,20 @@ import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.annotations.concurrency.GuardedBy; import com.android.sdklib.repository.descriptors.PkgType; -import com.android.sdklib.repository.local.LocalPkgInfo; import com.android.tools.idea.sdk.remote.RemotePkgInfo; import com.android.tools.idea.sdk.remote.RemoteSdk; -import com.android.tools.idea.sdk.remote.Update; -import com.android.tools.idea.sdk.remote.UpdateResult; import com.android.tools.idea.sdk.remote.internal.sources.SdkSources; import com.android.utils.ILogger; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.reference.SoftReference; -import com.intellij.util.concurrency.FutureResult; import com.intellij.util.concurrency.Semaphore; import org.jetbrains.android.sdk.AndroidSdkData; @@ -48,25 +40,19 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; public class SdkState { - /** Default expiration delay is 24 hours. */ - public final static long DEFAULT_EXPIRATION_PERIOD_MS = 24 * 3600 * 1000; + public final static long DEFAULT_EXPIRATION_PERIOD_MS = TimeUnit.DAYS.toMillis(1); private static final Logger LOG = Logger.getInstance("#com.android.tools.idea.sdk.SdkState"); - @GuardedBy(value = "sSdkStates") - private static final Set<SoftReference<SdkState>> sSdkStates = new HashSet<SoftReference<SdkState>>(); + @GuardedBy(value = "sSdkStates") private static final Set<SoftReference<SdkState>> sSdkStates = new HashSet<SoftReference<SdkState>>(); - @Nullable - private final AndroidSdkData mySdkData; + @Nullable private final AndroidSdkData mySdkData; private final RemoteSdk myRemoteSdk; - private LocalPkgInfo[] myLocalPkgInfos = new LocalPkgInfo[0]; - private SdkSources mySources; - private UpdateResult myUpdates; - private Multimap<PkgType, RemotePkgInfo> myRemotePkgs; + private SdkPackages myPackages = null; private long myLastRefreshMs; private LoadTask myTask; @@ -78,6 +64,16 @@ public class SdkState { myRemoteSdk = new RemoteSdk(new LogWrapper(Logger.getInstance(SdkState.class))); } + /** + * This shouldn't be needed unless interacting with the internals of the remote sdk. + * + * @return + */ + @NonNull + public RemoteSdk getRemoteSdk() { + return myRemoteSdk; + } + @NonNull public static SdkState getInstance(@Nullable AndroidSdkData sdkData) { synchronized (sSdkStates) { @@ -106,17 +102,9 @@ public class SdkState { } @NonNull - public LocalPkgInfo[] getLocalPkgInfos() { - return myLocalPkgInfos; - } - - public Multimap<PkgType, RemotePkgInfo> getRemotePkgInfos() { - return myRemotePkgs; - } - - @Nullable - public UpdateResult getUpdates() { - return myUpdates; + public SdkPackages getPackages() { + assert myPackages != null; + return myPackages; } public boolean loadAsync(long timeoutMs, @@ -152,8 +140,12 @@ public class SdkState { myTask = new LoadTask(canBeCancelled, onLocalComplete, onSuccess, onError, forceRefresh, sync); } - ProgressWindow progress = new BackgroundableProcessIndicator(myTask); - myTask.setProgress(progress); + if (!ApplicationManager.getApplication().isDispatchThread()) { + // not dispatch thread, assume progress is being handled elsewhere. Just run the task. + myTask.run(new EmptyProgressIndicator()); + return true; + } + ProgressManager.getInstance().run(myTask); return true; @@ -180,6 +172,10 @@ public class SdkState { onSuccesses.add(complete); onErrors.add(complete); boolean result = load(timeoutMs, canBeCancelled, onLocalCompletes, onSuccesses, onErrors, forceRefresh, true); + if (!ApplicationManager.getApplication().isDispatchThread()) { + // not dispatch thread, assume progress is being handled elsewhere. We don't have to wait since load() ran in-thread. + return result; + } ProgressManager pm = ProgressManager.getInstance(); ProgressIndicator indicator = pm.getProgressIndicator(); indicator = indicator == null ? new ProgressWindow(false, false, null) : indicator; @@ -228,7 +224,8 @@ public class SdkState { public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) { if (msgFormat == null && t != null) { myIndicator.setText2(t.toString()); - } else if (msgFormat != null) { + } + else if (msgFormat != null) { myIndicator.setText2(String.format(msgFormat, args)); } } @@ -287,14 +284,11 @@ public class SdkState { @Override public void run(@NonNull ProgressIndicator indicator) { - assert myProgress != null; boolean success = false; try { IndicatorLogger logger = new IndicatorLogger(indicator); - ApplicationEx app = ApplicationManagerEx.getApplicationEx(); - SdkLifecycleListener notifier = app.getMessageBus().syncPublisher(SdkLifecycleListener.TOPIC); - + myPackages = new SdkPackages(); if (mySdkData != null) { // fetch local sdk indicator.setText("Loading local SDK..."); @@ -302,8 +296,7 @@ public class SdkState { if (myForceRefresh) { mySdkData.getLocalSdk().clearLocalPkg(PkgType.PKG_ALL); } - myLocalPkgInfos = mySdkData.getLocalSdk().getPkgsInfos(PkgType.PKG_ALL); - notifier.localSdkLoaded(mySdkData); + myPackages.setLocalPkgInfos(mySdkData.getLocalSdk().getPkgsInfos(PkgType.PKG_ALL)); indicator.setFraction(0.25); } if (indicator.isCanceled()) { @@ -315,11 +308,10 @@ public class SdkState { } myOnLocalCompletes.clear(); } - // fetch sdk repository sources. indicator.setText("Find SDK Repository..."); indicator.setText2(""); - mySources = myRemoteSdk.fetchSources(myForceRefresh ? 0 : RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, logger); + SdkSources sources = myRemoteSdk.fetchSources(myForceRefresh ? 0 : RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, logger); indicator.setFraction(0.50); if (indicator.isCanceled()) { @@ -328,18 +320,15 @@ public class SdkState { // fetch remote sdk indicator.setText("Check SDK Repository..."); indicator.setText2(""); - myRemotePkgs = myRemoteSdk.fetch(mySources, logger); - notifier.remoteSdkLoaded(mySdkData); + Multimap<PkgType, RemotePkgInfo> remotes = myRemoteSdk.fetch(sources, logger); + // compute updates + indicator.setText("Compute SDK updates..."); indicator.setFraction(0.75); - + myPackages.setRemotePkgInfos(remotes); if (indicator.isCanceled()) { return; } - // compute updates - indicator.setText("Compute SDK updates..."); indicator.setText2(""); - myUpdates = Update.computeUpdates(myLocalPkgInfos, myRemotePkgs); - notifier.updatesComputed(mySdkData); indicator.setFraction(1.0); if (indicator.isCanceled()) { diff --git a/android/src/com/android/tools/idea/sdk/remote/UpdatablePkgInfo.java b/android/src/com/android/tools/idea/sdk/remote/UpdatablePkgInfo.java index ff082dbcc31..e25aa70d05e 100644 --- a/android/src/com/android/tools/idea/sdk/remote/UpdatablePkgInfo.java +++ b/android/src/com/android/tools/idea/sdk/remote/UpdatablePkgInfo.java @@ -16,38 +16,71 @@ package com.android.tools.idea.sdk.remote; import com.android.annotations.NonNull; +import com.android.sdklib.repository.descriptors.IPkgDesc; import com.android.sdklib.repository.local.LocalPkgInfo; import org.jetbrains.annotations.Nullable; /** - * Created by jbakermalone on 4/3/15. + * Represents a (revisionless) package, either local, remote, or both. If both a local and remote package are specified, + * they should represent exactly the same package, excepting the revision. That is, the result of installing the remote package + * should be (a possibly updated version of) the local package. */ public class UpdatablePkgInfo implements Comparable<UpdatablePkgInfo> { - private final LocalPkgInfo myLocalInfo; - private RemotePkgInfo myUpdate; + private LocalPkgInfo myLocalInfo; + private RemotePkgInfo myRemoteInfo; - public UpdatablePkgInfo(@NonNull LocalPkgInfo localPkg, @Nullable RemotePkgInfo update) { + public UpdatablePkgInfo(@NonNull LocalPkgInfo localInfo) { + init(localInfo, null); + } + + public UpdatablePkgInfo(@NonNull RemotePkgInfo remoteInfo) { + init(null, remoteInfo); + } + + public UpdatablePkgInfo(@NonNull LocalPkgInfo localInfo, @NonNull RemotePkgInfo remoteInfo) { + init(localInfo, remoteInfo); + } + + private void init(@Nullable LocalPkgInfo localPkg, @Nullable RemotePkgInfo remotePkg) { + assert localPkg != null || remotePkg != null; myLocalInfo = localPkg; - myUpdate = update; + myRemoteInfo = remotePkg; } - public void setUpdate(@NonNull RemotePkgInfo update) { - assert myUpdate == null; - myUpdate = update; + public void setRemote(@NonNull RemotePkgInfo remote) { + assert myRemoteInfo == null; + myRemoteInfo = remote; } - @NonNull + @Nullable public LocalPkgInfo getLocalInfo() { return myLocalInfo; } @Nullable - public RemotePkgInfo getUpdate() { - return myUpdate; + public RemotePkgInfo getRemote() { + return myRemoteInfo; + } + + public boolean hasRemote() { + return myRemoteInfo != null; + } + + public boolean hasLocal() { + return myLocalInfo != null; } @Override public int compareTo(UpdatablePkgInfo o) { - return getLocalInfo().compareTo(o.getLocalInfo()); + return getPkgDesc().compareTo(o.getPkgDesc()); + } + + public IPkgDesc getPkgDesc() { + return myLocalInfo == null ? myRemoteInfo.getPkgDesc() : myLocalInfo.getDesc(); + } + + public boolean isUpdate() { + return myLocalInfo != null && myRemoteInfo != null && + myRemoteInfo.getPkgDesc().getPreciseRevision().compareTo(myLocalInfo.getDesc().getPreciseRevision()) > 0; } } diff --git a/android/src/com/android/tools/idea/sdk/remote/Update.java b/android/src/com/android/tools/idea/sdk/remote/Update.java deleted file mode 100755 index bc6213060f4..00000000000 --- a/android/src/com/android/tools/idea/sdk/remote/Update.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2015 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.tools.idea.sdk.remote; - -import com.android.annotations.NonNull; -import com.android.sdklib.repository.descriptors.IPkgDesc; -import com.android.sdklib.repository.descriptors.PkgType; -import com.android.sdklib.repository.local.LocalPkgInfo; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; - -import java.util.Set; - - -/** - * Helper methods to compute updates available for local packages. - */ -public abstract class Update { - - public static UpdateResult computeUpdates(@NonNull LocalPkgInfo[] localPkgs, - @NonNull Multimap<PkgType, RemotePkgInfo> remotePkgs) { - UpdatablePkgInfo[] updatablePkgInfos = new UpdatablePkgInfo[localPkgs.length]; - for (int i = 0; i < localPkgs.length; i++) { - updatablePkgInfos[i] = new UpdatablePkgInfo(localPkgs[i], null); - } - UpdateResult result = new UpdateResult(); - Set<RemotePkgInfo> updates = Sets.newTreeSet(); - - // Find updates to locally installed packages - for (UpdatablePkgInfo info : updatablePkgInfos) { - RemotePkgInfo update = findUpdate(info, remotePkgs, result); - if (update != null) { - info.setUpdate(update); - updates.add(update); - } - } - - // Find new packages not yet installed - nextRemote: for (RemotePkgInfo remote : remotePkgs.values()) { - if (updates.contains(remote)) { - // if package is already a known update, it's not new. - continue nextRemote; - } - IPkgDesc remoteDesc = remote.getPkgDesc(); - for (UpdatablePkgInfo info : updatablePkgInfos) { - IPkgDesc localDesc = info.getLocalInfo().getDesc(); - if (remoteDesc.compareTo(localDesc) == 0 || remoteDesc.isUpdateFor(localDesc)) { - // if package is same as an installed or is an update for an installed - // one, then it's not new. - continue nextRemote; - } - } - - result.addNewPkgs(remote); - } - - return result; - } - - private static RemotePkgInfo findUpdate(@NonNull UpdatablePkgInfo info, - @NonNull Multimap<PkgType, RemotePkgInfo> remotePkgs, - @NonNull UpdateResult result) { - RemotePkgInfo currUpdatePkg = null; - IPkgDesc currUpdateDesc = null; - IPkgDesc localDesc = info.getLocalInfo().getDesc(); - - for (RemotePkgInfo remote: remotePkgs.get(localDesc.getType())) { - IPkgDesc remoteDesc = remote.getPkgDesc(); - if ((currUpdateDesc == null && remoteDesc.isUpdateFor(localDesc)) || - (currUpdateDesc != null && remoteDesc.isUpdateFor(currUpdateDesc))) { - currUpdatePkg = remote; - currUpdateDesc = remoteDesc; - } - } - - if (currUpdatePkg != null) { - result.addUpdatedPkgs(info); - } - - return currUpdatePkg; - } - -} diff --git a/android/src/com/android/tools/idea/sdk/remote/UpdateResult.java b/android/src/com/android/tools/idea/sdk/remote/UpdateResult.java deleted file mode 100755 index b6ab1485d86..00000000000 --- a/android/src/com/android/tools/idea/sdk/remote/UpdateResult.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 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.tools.idea.sdk.remote; - -import com.android.annotations.NonNull; -import com.android.sdklib.repository.local.LocalPkgInfo; -import com.google.common.collect.Sets; - -import java.util.Set; - - -/** - * Results from {@link Update#computeUpdates(LocalPkgInfo[], com.google.common.collect.Multimap)}. - */ -public final class UpdateResult { - private final Set<UpdatablePkgInfo> mUpdatedPkgs = Sets.newTreeSet(); - private final Set<RemotePkgInfo> mNewPkgs = Sets.newTreeSet(); - private final long mTimestampMs; - - public UpdateResult() { - mTimestampMs = System.currentTimeMillis(); - } - - /** - * Returns the timestamp (in {@link System#currentTimeMillis()} time) when this object was created. - */ - public long getTimestampMs() { - return mTimestampMs; - } - - /** - * Returns the set of packages that have local updates available. - * Use {@link LocalPkgInfo#getUpdate()} to retrieve the computed updated candidate. - * - * @return A non-null, possibly empty list of update candidates. - */ - @NonNull - public Set<UpdatablePkgInfo> getUpdatedPkgs() { - return mUpdatedPkgs; - } - - /** - * Returns the set of new remote packages that are not locally present - * and that the user could install. - * - * @return A non-null, possibly empty list of new install candidates. - */ - @NonNull - public Set<RemotePkgInfo> getNewPkgs() { - return mNewPkgs; - } - - /** - * Add a package to the set of packages with available updates. - * - * @param pkgInfo The {@link LocalPkgInfo} which has an available update. - */ - void addUpdatedPkgs(@NonNull UpdatablePkgInfo pkgInfo) { - mUpdatedPkgs.add(pkgInfo); - } - - /** - * Add a package to the set of new remote packages that are not locally present - * and that the user could install. - * - * @param pkgInfo The {@link RemotePkgInfo} which has an available update. - */ - void addNewPkgs(@NonNull RemotePkgInfo pkgInfo) { - mNewPkgs.add(pkgInfo); - } -} diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/archives/Archive.java b/android/src/com/android/tools/idea/sdk/remote/internal/archives/Archive.java index 0d34b5b50a9..25191050f71 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/archives/Archive.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/archives/Archive.java @@ -56,7 +56,7 @@ public class Archive implements IDescription, Comparable<Archive> { * @param checksum The expected checksum string of the archive. Currently only the * {@link ChecksumType#SHA1} format is supported. */ - public Archive(@Nullable RemotePkgInfo pkg, @Nullable ArchFilter archFilter, @Nullable String url, long size, @NonNull String checksum) { + public Archive(@NonNull RemotePkgInfo pkg, @Nullable ArchFilter archFilter, @Nullable String url, long size, @NonNull String checksum) { mPackage = pkg; mArchFilter = archFilter != null ? archFilter : new ArchFilter(null); mUrl = url == null ? null : url.trim(); @@ -76,7 +76,7 @@ public class Archive implements IDescription, Comparable<Archive> { * Returns the package that created and owns this archive. * It should generally not be null. */ - @Nullable + @NonNull public RemotePkgInfo getParentPackage() { return mPackage; } diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/sources/SdkSources.java b/android/src/com/android/tools/idea/sdk/remote/internal/sources/SdkSources.java index 34a94013ebb..7f908879a26 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/sources/SdkSources.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/sources/SdkSources.java @@ -16,20 +16,19 @@ package com.android.tools.idea.sdk.remote.internal.sources; +import com.android.annotations.concurrency.GuardedBy; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.repository.SdkSysImgConstants; import com.android.utils.ILogger; +import com.google.common.collect.Lists; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.Iterator; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; /** * A list of sdk-repository and sdk-addon sources, sorted by {@link SdkSourceCategory}. @@ -39,10 +38,12 @@ public class SdkSources { private static final String KEY_COUNT = "count"; private static final String KEY_SRC = "src"; + private static final String KEY_DISPLAY = "disp"; private static final String SRC_FILENAME = "repositories.cfg"; //$NON-NLS-1$ - private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mSources = + @GuardedBy("itself") + private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mySources = new EnumMap<SdkSourceCategory, ArrayList<SdkSource>>(SdkSourceCategory.class); public SdkSources() { @@ -57,11 +58,11 @@ public class SdkSources { * at the end, not for every single addition. */ public void add(SdkSourceCategory category, SdkSource source) { - synchronized (mSources) { - ArrayList<SdkSource> list = mSources.get(category); + synchronized (mySources) { + ArrayList<SdkSource> list = mySources.get(category); if (list == null) { list = new ArrayList<SdkSource>(); - mSources.put(category, list); + mySources.put(category, list); } list.add(source); @@ -69,14 +70,28 @@ public class SdkSources { } /** + * Replaces the current collection of sources corresponding to a particular category with the given collection. + * <p/> + * Implementation detail: {@link SdkSources} doesn't invoke {@link #notifyChangeListeners()} + * directly. Callers who use {@code set()} are responsible for notifying the listeners once + * they are done modifying the sources list. The intent is to notify the listeners only once + * at the end, not for every single addition. + */ + public void set(SdkSourceCategory category, Collection<SdkSource> sources) { + synchronized (mySources) { + mySources.put(category, Lists.newArrayList(sources)); + } + } + + /** * Removes a source from the Sources list. * <p/> * Callers who remove entries are responsible for notifying the listeners using * {@link #notifyChangeListeners()} once they are done modifying the sources list. */ public void remove(SdkSource source) { - synchronized (mSources) { - Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it = mSources.entrySet().iterator(); + synchronized (mySources) { + Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it = mySources.entrySet().iterator(); while (it.hasNext()) { Entry<SdkSourceCategory, ArrayList<SdkSource>> entry = it.next(); ArrayList<SdkSource> list = entry.getValue(); @@ -98,8 +113,8 @@ public class SdkSources { * {@link #notifyChangeListeners()} once they are done modifying the sources list. */ public void removeAll(SdkSourceCategory category) { - synchronized (mSources) { - mSources.remove(category); + synchronized (mySources) { + mySources.remove(category); } } @@ -117,8 +132,8 @@ public class SdkSources { cats.add(cat); } else { - synchronized (mSources) { - ArrayList<SdkSource> list = mSources.get(cat); + synchronized (mySources) { + ArrayList<SdkSource> list = mySources.get(cat); if (list != null && !list.isEmpty()) { cats.add(cat); } @@ -134,8 +149,8 @@ public class SdkSources { * Might return an empty array, but never returns null. */ public SdkSource[] getSources(SdkSourceCategory category) { - synchronized (mSources) { - ArrayList<SdkSource> list = mSources.get(category); + synchronized (mySources) { + ArrayList<SdkSource> list = mySources.get(category); if (list == null) { return new SdkSource[0]; } @@ -149,8 +164,8 @@ public class SdkSources { * Returns true if there are sources for the given category. */ public boolean hasSources(SdkSourceCategory category) { - synchronized (mSources) { - ArrayList<SdkSource> list = mSources.get(category); + synchronized (mySources) { + ArrayList<SdkSource> list = mySources.get(category); return list != null && !list.isEmpty(); } } @@ -159,17 +174,17 @@ public class SdkSources { * Returns an array of the sources across all categories. This is never null. */ public SdkSource[] getAllSources() { - synchronized (mSources) { + synchronized (mySources) { int n = 0; - for (ArrayList<SdkSource> list : mSources.values()) { + for (ArrayList<SdkSource> list : mySources.values()) { n += list.size(); } SdkSource[] sources = new SdkSource[n]; int i = 0; - for (ArrayList<SdkSource> list : mSources.values()) { + for (ArrayList<SdkSource> list : mySources.values()) { for (SdkSource source : list) { sources[i++] = source; } @@ -186,8 +201,8 @@ public class SdkSources { * the remote package list. */ public void clearAllPackages() { - synchronized (mSources) { - for (ArrayList<SdkSource> list : mSources.values()) { + synchronized (mySources) { + for (ArrayList<SdkSource> list : mySources.values()) { for (SdkSource source : list) { source.clearPackages(); } @@ -205,8 +220,8 @@ public class SdkSources { */ public SdkSourceCategory getCategory(SdkSource source) { if (source != null) { - synchronized (mSources) { - for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mSources.entrySet()) { + synchronized (mySources) { + for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mySources.entrySet()) { if (entry.getValue().contains(source)) { return entry.getKey(); } @@ -227,8 +242,8 @@ public class SdkSources { * The search is O(N), which should be acceptable on the expectedly small source list. */ public boolean hasSourceUrl(SdkSource source) { - synchronized (mSources) { - for (ArrayList<SdkSource> list : mSources.values()) { + synchronized (mySources) { + for (ArrayList<SdkSource> list : mySources.values()) { for (SdkSource s : list) { if (s.equals(source)) { return true; @@ -250,8 +265,8 @@ public class SdkSources { * The search is O(N), which should be acceptable on the expectedly small source list. */ public boolean hasSourceUrl(SdkSourceCategory category, SdkSource source) { - synchronized (mSources) { - ArrayList<SdkSource> list = mSources.get(category); + synchronized (mySources) { + ArrayList<SdkSource> list = mySources.get(category); if (list != null) { for (SdkSource s : list) { if (s.equals(source)) { @@ -276,7 +291,7 @@ public class SdkSources { // In most cases we do these operation from the UI thread so it's not really // that necessary. This is more a protection in case of someone calls this // from a worker thread by mistake. - synchronized (mSources) { + synchronized (mySources) { // Remove all existing user sources removeAll(SdkSourceCategory.USER_ADDONS); @@ -295,6 +310,7 @@ public class SdkSources { for (int i = 0; i < count; i++) { String url = props.getProperty(String.format("%s%02d", KEY_SRC, i)); //$NON-NLS-1$ + String disp = props.getProperty(String.format("%s%02d", KEY_DISPLAY, i)); //$NON-NLS-1$ if (url != null) { // FIXME: this code originally only dealt with add-on XML sources. // Now we'd like it to deal with system-image sources too, but we @@ -307,10 +323,10 @@ public class SdkSources { // the URI has been fetched. SdkSource s; if (url.endsWith(SdkSysImgConstants.URL_DEFAULT_FILENAME)) { - s = new SdkSysImgSource(url, null/*uiName*/); + s = new SdkSysImgSource(url, disp); } else { - s = new SdkAddonSource(url, null/*uiName*/); + s = new SdkAddonSource(url, disp); } if (!hasSourceUrl(s)) { add(SdkSourceCategory.USER_ADDONS, s); @@ -351,7 +367,7 @@ public class SdkSources { */ public void saveUserAddons(ILogger log) { // See the implementation detail note in loadUserAddons() about the synchronization. - synchronized (mSources) { + synchronized (mySources) { FileOutputStream fos = null; try { String folder = AndroidLocation.getFolder(); @@ -365,6 +381,10 @@ public class SdkSources { for (SdkSource s : getSources(SdkSourceCategory.USER_ADDONS)) { props.setProperty(String.format("%s%02d", KEY_SRC, count), //$NON-NLS-1$ s.getUrl()); + if (s.getUiName() != null) { + props.setProperty(String.format("%s%02d", KEY_DISPLAY, count), //$NON-NLS-1$ + s.getUiName()); + } count++; } props.setProperty(KEY_COUNT, Integer.toString(count)); diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/PkgItem.java b/android/src/com/android/tools/idea/sdk/remote/internal/updater/PkgItem.java deleted file mode 100755 index 02ea2887ec6..00000000000 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/PkgItem.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2015 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.tools.idea.sdk.remote.internal.updater; - -import com.android.annotations.Nullable; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.repository.FullRevision; -import com.android.sdklib.repository.local.LocalPkgInfo; -import com.android.tools.idea.sdk.remote.RemotePkgInfo; -import com.android.tools.idea.sdk.remote.internal.archives.Archive; -import com.android.tools.idea.sdk.remote.internal.packages.IAndroidVersionProvider; -import com.android.tools.idea.sdk.remote.internal.sources.SdkSource; -import com.google.common.base.Objects; - -/** - * A {@link PkgItem} represents one main {@link Package} combined with its state - * and an optional update package. - * <p/> - * The main package is final and cannot change since it's what "defines" this PkgItem. - * The state or update package can change later. - */ -public class PkgItem implements Comparable<PkgItem> { - private final PkgState mState; - private final LocalPkgInfo mMainPkg; - private RemotePkgInfo mUpdatePkg; - private boolean mChecked; - - /** - * The state of the a given {@link PkgItem}, that is the relationship between - * a given remote package and the local repository. - */ - public enum PkgState { - // Implementation detail: if this is changed then PackageDiffLogic#STATES - // and PackageDiffLogic#processSource() need to be changed accordingly. - - /** - * Package is locally installed and may or may not have an update. - */ - INSTALLED, - - /** - * There's a new package available on the remote site that isn't installed locally. - */ - NEW - } - - /** - * Create a new {@link PkgItem} for this main package. - * The main package is final and cannot change since it's what "defines" this PkgItem. - * The state or update package can change later. - */ - public PkgItem(LocalPkgInfo mainPkg, PkgState state) { - mMainPkg = mainPkg; - mState = state; - assert mMainPkg != null; - } - - public boolean isObsolete() { - return mMainPkg.getDesc().isObsolete(); - } - - public boolean isChecked() { - return mChecked; - } - - public void setChecked(boolean checked) { - mChecked = checked; - } - - public RemotePkgInfo getUpdatePkg() { - return mUpdatePkg; - } - - public boolean hasUpdatePkg() { - return mUpdatePkg != null; - } - - public String getName() { - return mMainPkg.getListDescription(); - } - - public FullRevision getRevision() { - return mMainPkg.getDesc().getFullRevision(); - } - - public LocalPkgInfo getMainPackage() { - return mMainPkg; - } - - public PkgState getState() { - return mState; - } - - @Nullable - public SdkSource getSource() { - return mUpdatePkg == null ? null : mUpdatePkg.getParentSource(); - } - - @Nullable - public AndroidVersion getAndroidVersion() { - return mMainPkg instanceof IAndroidVersionProvider ? ((IAndroidVersionProvider)mMainPkg).getAndroidVersion() : null; - } - - @Nullable - public Archive[] getArchives() { - return mUpdatePkg == null ? null : mUpdatePkg.getArchives(); - } - - @Override - public int compareTo(PkgItem pkg) { - return getMainPackage().compareTo(pkg.getMainPackage()); - } - - /** - * Equality is defined as {@link #isSameItemAs(PkgItem)}: state, main package - * and update package must be the similar. - */ - @Override - public boolean equals(Object obj) { - if (!(obj instanceof PkgItem)) { - return false; - } - PkgItem other = (PkgItem)obj; - return mMainPkg.equals(other.mMainPkg) - && Objects.equal(mUpdatePkg, other.mUpdatePkg) - && mState.equals(other.mState); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mState == null) ? 0 : mState.hashCode()); - result = prime * result + ((mMainPkg == null) ? 0 : mMainPkg.hashCode()); - result = prime * result + ((mUpdatePkg == null) ? 0 : mUpdatePkg.hashCode()); - return result; - } - - /** - * Check whether the 'pkg' argument is an update for this package. - * If it is, record it as an updating package. - * If there's already an updating package, only keep the most recent update. - * Returns true if it is update (even if there was already an update and this - * ended up not being the most recent), false if incompatible or not an update. - * <p/> - * This should only be used for installed packages. - */ - public boolean mergeUpdate(RemotePkgInfo pkg) { - if (mUpdatePkg == pkg) { - return true; - } - if (pkg.canUpdate(mMainPkg) == RemotePkgInfo.UpdateInfo.UPDATE) { - if (mUpdatePkg == null) { - mUpdatePkg = pkg; - } - return true; - } - - return false; - } - - public void removeUpdate() { - mUpdatePkg = null; - } - - /** - * Returns a string representation of this item, useful when debugging. - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('<'); - - if (mChecked) { - sb.append(" * "); //$NON-NLS-1$ - } - - sb.append(mState.toString()); - - if (mMainPkg != null) { - sb.append(", pkg:"); //$NON-NLS-1$ - sb.append(mMainPkg.toString()); - } - - if (mUpdatePkg != null) { - sb.append(", updated by:"); //$NON-NLS-1$ - sb.append(mUpdatePkg.toString()); - } - - sb.append('>'); - return sb.toString(); - } -} diff --git a/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java b/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java index 9a29d7f2bde..53deade3f8d 100755 --- a/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java +++ b/android/src/com/android/tools/idea/sdk/remote/internal/updater/UpdaterData.java @@ -606,7 +606,7 @@ public class UpdaterData implements IUpdaterData { SdkState state = SdkState.getInstance(AndroidSdkUtils.tryToChooseAndroidSdk()); state.loadSynchronously(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, false, null, null, null, false); List<ArchiveInfo> result = Lists.newArrayList(); - for (RemotePkgInfo remote : state.getRemotePkgInfos().values()) { + for (RemotePkgInfo remote : state.getPackages().getRemotePkgInfos().values()) { if (includeAll || !remote.isObsolete()) { for (Archive archive : remote.getArchives()) { if (archive.isCompatible()) { diff --git a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java index 4cb9afb906d..d50c865d810 100755 --- a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java +++ b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java @@ -26,6 +26,7 @@ import com.android.tools.idea.wizard.DynamicWizardStepWithDescription; import com.android.utils.ILogger; import com.google.common.collect.Lists; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.PerformInBackgroundOption; import com.intellij.openapi.progress.ProgressIndicator; @@ -109,6 +110,10 @@ public class SmwOldApiDirectInstall extends DynamicWizardStepWithDescription { Runnable onSdkAvailable = new Runnable() { @Override public void run() { + if (!ApplicationManager.getApplication().isDispatchThread()) { + ApplicationManager.getApplication().invokeLater(this); + return; + } // TODO: since the local SDK has been parsed, this is now a good time // to filter requestedPackages to remove current installed packages. // That's because on Windows trying to update some of the packages in-place diff --git a/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java b/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java index 24d88da9b5e..f674c17d134 100644 --- a/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java +++ b/android/src/com/android/tools/idea/welcome/install/ComponentInstaller.java @@ -22,8 +22,7 @@ import com.android.sdklib.repository.local.LocalPkgInfo; import com.android.sdklib.repository.local.LocalSdk; import com.android.tools.idea.sdk.remote.RemotePkgInfo; import com.android.tools.idea.sdk.remote.UpdatablePkgInfo; -import com.android.tools.idea.sdk.remote.Update; -import com.android.tools.idea.sdk.remote.UpdateResult; +import com.android.tools.idea.sdk.SdkPackages; import com.android.tools.idea.sdk.remote.internal.updater.SdkUpdaterNoWindow; import com.android.utils.ILogger; import com.android.utils.NullLogger; @@ -69,7 +68,7 @@ public final class ComponentInstaller { private Iterable<LocalPkgInfo> getOldPackages(Collection<LocalPkgInfo> installed) { if (myRemotePackages != null) { LocalPkgInfo[] packagesArray = ArrayUtil.toObjectArray(installed, LocalPkgInfo.class); - UpdateResult result = Update.computeUpdates(packagesArray, myRemotePackages); + SdkPackages result = new SdkPackages(packagesArray, myRemotePackages); return Iterables.transform(result.getUpdatedPkgs(), new Function<UpdatablePkgInfo, LocalPkgInfo>() { @Override public LocalPkgInfo apply(@Nullable UpdatablePkgInfo input) { @@ -100,7 +99,7 @@ public final class ComponentInstaller { * * @param manager SDK manager instance or <code>null</code> if this is a new install. * @param defaultUpdateAvailable If true, and if remote package information is not available, assume each package may have an update and - * try to reinstall. If false and remote package information not available, assume no updates are available. + * try to reinstall. If false and remote package information not available, assume no updates are available. */ public ArrayList<String> getPackagesToInstall(@Nullable SdkManager manager, @NotNull Iterable<? extends InstallableComponent> components, boolean defaultUpdateAvailable) { diff --git a/android/src/com/android/tools/idea/welcome/wizard/AndroidStudioWelcomeScreenProvider.java b/android/src/com/android/tools/idea/welcome/wizard/AndroidStudioWelcomeScreenProvider.java index 422443c0e4b..7559a740bf8 100644 --- a/android/src/com/android/tools/idea/welcome/wizard/AndroidStudioWelcomeScreenProvider.java +++ b/android/src/com/android/tools/idea/welcome/wizard/AndroidStudioWelcomeScreenProvider.java @@ -160,7 +160,7 @@ public final class AndroidStudioWelcomeScreenProvider implements WelcomeScreenPr SdkState state = SdkState.getInstance(AndroidSdkUtils.tryToChooseAndroidSdk()); state.loadSynchronously(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, false, null, null, null, true); - return state.getRemotePkgInfos(); + return state.getPackages().getRemotePkgInfos(); } @Override diff --git a/android/src/com/android/tools/idea/wizard/ConfigureFormFactorStep.java b/android/src/com/android/tools/idea/wizard/ConfigureFormFactorStep.java index e4638d017b2..c46aa7e466f 100755 --- a/android/src/com/android/tools/idea/wizard/ConfigureFormFactorStep.java +++ b/android/src/com/android/tools/idea/wizard/ConfigureFormFactorStep.java @@ -261,8 +261,7 @@ public class ConfigureFormFactorStep extends DynamicWizardStepWithHeaderAndDescr ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { - - List<RemotePkgInfo> packageList = Lists.newArrayList(state.getUpdates().getNewPkgs()); + List<RemotePkgInfo> packageList = Lists.newArrayList(state.getPackages().getNewPkgs()); Collections.sort(packageList); Iterator<RemotePkgInfo> result = Iterables.filter(packageList, FormFactorUtils.getMinSdkPackageFilter(formFactor, minSdkLevel)).iterator(); diff --git a/sdk-updates/sdk-updates.iml b/sdk-updates/sdk-updates.iml new file mode 100644 index 00000000000..d8cb223b98f --- /dev/null +++ b/sdk-updates/sdk-updates.iml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="platform-impl" /> + <orderEntry type="module" module-name="android" /> + <orderEntry type="module" module-name="lang-api" /> + </component> +</module>
\ No newline at end of file diff --git a/sdk-updates/src/META-INF/plugin.xml b/sdk-updates/src/META-INF/plugin.xml new file mode 100644 index 00000000000..3408fd88dad --- /dev/null +++ b/sdk-updates/src/META-INF/plugin.xml @@ -0,0 +1,38 @@ +<!-- + ~ Copyright (C) 2015 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. + --> +<idea-plugin version="2"> + <id>com.android.tools.idea.updater</id> + <name>SDK Updater</name> + <version>1.0</version> + <vendor>JetBrains</vendor> + + <description><![CDATA[ + Android SDK Updater Plugin + ]]></description> + + <!-- please see http://confluence.jetbrains.com/display/IDEADEV/Build+Number+Ranges for description --> + <idea-version since-build="131"/> + + <depends>org.jetbrains.android</depends> + + <application-components> + <component> + <implementation-class>com.android.tools.idea.updater.AndroidSdkUpdaterPlugin</implementation-class> + <interface-class>com.android.tools.idea.updater.AndroidSdkUpdaterPlugin</interface-class> + </component> + </application-components> + +</idea-plugin> diff --git a/sdk-updates/src/com/android/tools/idea/updater/AndroidSdkUpdaterPlugin.java b/sdk-updates/src/com/android/tools/idea/updater/AndroidSdkUpdaterPlugin.java new file mode 100644 index 00000000000..e85405e3f60 --- /dev/null +++ b/sdk-updates/src/com/android/tools/idea/updater/AndroidSdkUpdaterPlugin.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 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.tools.idea.updater; + +import com.intellij.ide.externalComponents.ExternalComponentManagerImpl; +import com.intellij.ide.externalComponents.UpdatableExternalComponent; +import com.intellij.openapi.components.ApplicationComponent; +import org.jetbrains.annotations.NotNull; + +/** + * Plugin to set up the android sdk {@link UpdatableExternalComponent} and + * {@link com.android.tools.idea.updater.configure.SdkUpdaterConfigurable}. + */ +public class AndroidSdkUpdaterPlugin implements ApplicationComponent { + @Override + public void initComponent() { + ExternalComponentManagerImpl.getInstance().registerComponentSource(new SdkComponentSource()); + } + + @Override + public void disposeComponent() { + // nothing + } + + @NotNull + @Override + public String getComponentName() { + return "Android Sdk Updater"; + } +} diff --git a/sdk-updates/src/com/android/tools/idea/updater/SdkComponentSource.java b/sdk-updates/src/com/android/tools/idea/updater/SdkComponentSource.java new file mode 100644 index 00000000000..78afb695e38 --- /dev/null +++ b/sdk-updates/src/com/android/tools/idea/updater/SdkComponentSource.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 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.tools.idea.updater; + +import com.android.sdklib.repository.descriptors.IPkgDesc; +import com.android.sdklib.repository.local.LocalSdk; +import com.android.tools.idea.sdk.SdkState; +import com.android.tools.idea.sdk.remote.RemoteSdk; +import com.android.tools.idea.sdk.remote.UpdatablePkgInfo; +import com.android.tools.idea.sdk.remote.internal.sources.SdkSources; +import com.android.tools.idea.sdk.wizard.SdkQuickfixWizard; +import com.android.tools.idea.wizard.DialogWrapperHost; +import com.android.utils.ILogger; +import com.android.utils.StdLogger; +import com.google.common.collect.Lists; +import com.intellij.ide.externalComponents.ExternalComponentSource; +import com.intellij.ide.externalComponents.UpdatableExternalComponent; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.android.sdk.AndroidSdkData; +import org.jetbrains.android.sdk.AndroidSdkUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; + +/** + * An {@link ExternalComponentSource} that retrieves information from the {@link LocalSdk} and {@link RemoteSdk} provided + * by the Android SDK. + */ +public class SdkComponentSource implements ExternalComponentSource { + SdkSources mySources; + SdkState mySdkState; + private static final ILogger ILOG = new StdLogger(StdLogger.Level.ERROR); + + private void initIfNecessary() { + if (mySdkState != null) { + return; + } + AndroidSdkData data = AndroidSdkUtils.tryToChooseAndroidSdk(); + assert data != null; + mySdkState = SdkState.getInstance(data); + + mySources = mySdkState.getRemoteSdk().fetchSources(RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, ILOG); + } + + /** + * Install the given new versions of components using the {@link SdkQuickfixWizard}. + * + * @param request The components to install. + */ + @Override + public void installUpdates(@NotNull Collection<UpdatableExternalComponent> request) { + final List<IPkgDesc> packages = Lists.newArrayList(); + for (UpdatableExternalComponent p : request) { + packages.add((IPkgDesc)p.getKey()); + } + SdkQuickfixWizard sdkQuickfixWizard = + new SdkQuickfixWizard(null, null, packages, new DialogWrapperHost(null, DialogWrapper.IdeModalityType.PROJECT)); + sdkQuickfixWizard.init(); + sdkQuickfixWizard.show(); + } + + /** + * Retrieves information on updates available from the {@link RemoteSdk}. + * + * @param indicator A {@code ProgressIndicator} that can be updated to show progress, or can be used to cancel the process. + * @return A collection of {@link UpdatablePackage}s corresponding to the currently installed Packages. + */ + @NotNull + @Override + public Collection<UpdatableExternalComponent> getAvailableVersions(ProgressIndicator indicator) { + return getComponents(indicator, true); + } + + /** + * Retrieves information on updates installed using the {@link LocalSdk}. + * + * @return A collection of {@link UpdatablePackage}s corresponding to the currently installed Packages. + */ + @NotNull + @Override + public Collection<UpdatableExternalComponent> getCurrentVersions() { + return getComponents(null, false); + } + + private Collection<UpdatableExternalComponent> getComponents(ProgressIndicator indicator, boolean remote) { + initIfNecessary(); + List<UpdatableExternalComponent> result = Lists.newArrayList(); + mySdkState.loadSynchronously(SdkState.DEFAULT_EXPIRATION_PERIOD_MS, true, null, null, null, true); + for (UpdatablePkgInfo info : mySdkState.getPackages().getConsolidatedPkgs()) { + if (remote) { + if (info.hasRemote()) { + result.add(new UpdatablePackage(info.getRemote().getPkgDesc())); + } + } + else { + if (info.hasLocal()) { + result.add(new UpdatablePackage(info.getLocalInfo().getDesc())); + } + } + } + return result; + } + + @NotNull + @Override + public String getName() { + return "Android SDK"; + } +} diff --git a/sdk-updates/src/com/android/tools/idea/updater/UpdatablePackage.java b/sdk-updates/src/com/android/tools/idea/updater/UpdatablePackage.java new file mode 100644 index 00000000000..4523b88a0d0 --- /dev/null +++ b/sdk-updates/src/com/android/tools/idea/updater/UpdatablePackage.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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.tools.idea.updater; + +import com.android.sdklib.repository.descriptors.IPkgDesc; +import com.intellij.ide.externalComponents.UpdatableExternalComponent; + +/** + * An {@link UpdatableExternalComponent} that corresponds to an + * {@link IPkgDesc} for a local or remote package. + */ +public class UpdatablePackage implements UpdatableExternalComponent { + private IPkgDesc myPackage; + + public UpdatablePackage(IPkgDesc p) { + myPackage = p; + } + + @Override + public IPkgDesc getKey() { + return myPackage; + } + + @Override + public boolean isUpdateFor(UpdatableExternalComponent c) { + if (c == null) { + return false; + } + Object otherKey = c.getKey(); + if (!(otherKey instanceof IPkgDesc)) { + return false; + } + return myPackage.isUpdateFor((IPkgDesc)otherKey); + } + + @Override + public String getName() { + return myPackage.getListDescription(); + } + + @Override + public String toString() { + return getName(); + } +} |