/*
* 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.android.annotations.Nullable;
import com.android.annotations.concurrency.GuardedBy;
import com.android.sdklib.internal.repository.sources.SdkSources;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.sdklib.repository.local.LocalPkgInfo;
import com.android.sdklib.repository.local.Update;
import com.android.sdklib.repository.local.UpdateResult;
import com.android.sdklib.repository.remote.RemotePkgInfo;
import com.android.sdklib.repository.remote.RemoteSdk;
import com.android.utils.ILogger;
import com.google.common.collect.Multimap;
import com.intellij.openapi.application.ApplicationManager;
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.impl.BackgroundableProcessIndicator;
import com.intellij.reference.SoftReference;
import org.jetbrains.android.sdk.AndroidSdkData;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SdkState {
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>>();
@NonNull
private final AndroidSdkData mySdkData;
private LocalPkgInfo[] myLocalPkgInfos = new LocalPkgInfo[0];
private SdkSources mySources;
private UpdateResult myUpdates;
private Multimap<PkgType, RemotePkgInfo> myRemotePkgs;
private long myLastRefreshMs;
private BackgroundableProcessIndicator myIndicator;
private SdkState(@NonNull AndroidSdkData sdkData) {
mySdkData = sdkData;
}
@NonNull
public static SdkState getInstance(@NonNull AndroidSdkData sdkData) {
synchronized (sSdkStates) {
for (Iterator<SoftReference<SdkState>> it = sSdkStates.iterator(); it.hasNext(); ) {
SoftReference<SdkState> ref = it.next();
SdkState s = ref.get();
if (s == null) {
it.remove();
continue;
}
// Note: check the cache for actual AndroidSdkData references, not equality.
if (s.mySdkData == sdkData) {
return s;
}
}
SdkState s = new SdkState(sdkData);
sSdkStates.add(new SoftReference<SdkState>(s));
return s;
}
}
@NonNull
public AndroidSdkData getSdkData() {
return mySdkData;
}
@NonNull
public LocalPkgInfo[] getLocalPkgInfos() {
return myLocalPkgInfos;
}
@Nullable
public UpdateResult getUpdates() {
return myUpdates;
}
public boolean loadAsync(long timeoutMs,
boolean canBeCancelled,
@Nullable Runnable onSuccess,
@Nullable Runnable onError) {
if (myIndicator != null) {
return false;
}
if (System.currentTimeMillis() - myLastRefreshMs < timeoutMs) {
return false;
}
LoadTask task = new LoadTask(canBeCancelled, onSuccess, onError);
myIndicator = new BackgroundableProcessIndicator(task);
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, myIndicator);
return true;
}
// -----
private static class IndicatorLogger implements ILogger {
@NonNull private final ProgressIndicator myIndicator;
public IndicatorLogger(@NonNull ProgressIndicator indicator) {
myIndicator = indicator;
}
@Override
public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
if (msgFormat == null && t != null) {
myIndicator.setText2(t.toString());
} else if (msgFormat != null) {
myIndicator.setText2(String.format(msgFormat, args));
}
}
@Override
public void warning(@NonNull String msgFormat, Object... args) {
myIndicator.setText2(String.format(msgFormat, args));
}
@Override
public void info(@NonNull String msgFormat, Object... args) {
myIndicator.setText2(String.format(msgFormat, args));
}
@Override
public void verbose(@NonNull String msgFormat, Object... args) {
// skip here, don't log verbose strings
}
}
private class LoadTask extends Task.Backgroundable {
@Nullable private final Runnable myOnSuccess;
@Nullable private final Runnable myOnError;
public LoadTask(boolean canBeCancelled,
@Nullable Runnable onSuccess,
@Nullable Runnable onError) {
super(null /*project*/,
"Loading Android SDK",
canBeCancelled,
PerformInBackgroundOption.ALWAYS_BACKGROUND);
myOnSuccess = onSuccess;
myOnError = onError;
}
@Override
public void run(@NonNull ProgressIndicator indicator) {
boolean success = false;
try {
IndicatorLogger logger = new IndicatorLogger(indicator);
ApplicationEx app = ApplicationManagerEx.getApplicationEx();
SdkLifecycleListener notifier = app.getMessageBus().syncPublisher(SdkLifecycleListener.TOPIC);
// fetch local sdk
indicator.setText("Loading local SDK...");
indicator.setText2("");
myLocalPkgInfos = mySdkData.getLocalSdk().getPkgsInfos(PkgType.PKG_ALL);
notifier.localSdkLoaded(mySdkData);
indicator.setFraction(0.25);
if (indicator.isCanceled()) {
return;
}
// fetch sdk repository sources.
indicator.setText("Find SDK Repository...");
indicator.setText2("");
mySources = mySdkData.getRemoteSdk().fetchSources(RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, logger);
indicator.setFraction(0.50);
if (indicator.isCanceled()) {
return;
}
// fetch remote sdk
indicator.setText("Check SDK Repository...");
indicator.setText2("");
myRemotePkgs = mySdkData.getRemoteSdk().fetch(mySources, logger);
notifier.remoteSdkLoaded(mySdkData);
indicator.setFraction(0.75);
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);
success = true;
if (myOnSuccess != null) {
ApplicationManager.getApplication().invokeLater(myOnSuccess);
}
}
finally {
myIndicator = null;
myLastRefreshMs = System.currentTimeMillis();
if (!success && myOnError != null) {
ApplicationManager.getApplication().invokeLater(myOnError);
}
}
}
}
}