blob: 8b194e3d4a08c2bc2bf96376ca42c29da0b78a07 [file] [log] [blame]
/*
* Copyright (C) 2017 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.instantapp;
import com.google.common.annotations.VisibleForTesting;
import com.android.repository.api.LocalPackage;
import com.android.sdklib.repository.AndroidSdkHandler;
import com.android.tools.analytics.AnalyticsSettings;
import com.android.tools.idea.sdk.AndroidSdks;
import com.android.tools.idea.sdk.progress.StudioLoggerProgressIndicator;
import com.android.tools.idea.wizard.model.ModelWizardDialog;
import com.google.android.instantapps.sdk.api.ExtendedSdk;
import com.google.android.instantapps.sdk.api.SdkLoader;
import com.google.android.instantapps.sdk.api.TelemetryManager;
import com.google.common.collect.ImmutableList;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import static com.android.SdkConstants.FD_EXTRAS;
import static com.android.tools.idea.sdk.wizard.SdkQuickfixUtils.createDialogForPaths;
/**
* Responsible for providing InstantApp SDK.
* It is registered as a service so it can be easily mocked.
*/
public class InstantAppSdks {
@NotNull private static final String INSTANT_APP_SDK_PATH = FD_EXTRAS + ";google;instantapps";
private static final String SDK_LIB_JAR_PATH = "tools/lib.jar";
@VisibleForTesting static final String UPGRADE_PROMPT_TEXT =
"Required Google Play Instant SDK must be updated to run this task. Do you want to update it now?";
@VisibleForTesting static final LoadInstantAppSdkException COULD_NOT_LOAD_NEW_SDK_EXCEPTION =
new LoadInstantAppSdkException("Could not load required version of the Google Play Instant SDK");
private ExtendedSdk cachedSdkLib = null;
@NotNull
public static InstantAppSdks getInstance() {
return ServiceManager.getService(InstantAppSdks.class);
}
/**
* Attempts to load the Google Play Instant SDK.
*
* If the SDK is missing, it will trigger an install. Failing that, it will throw an exception.
*/
@NotNull
public File getOrInstallInstantAppSdk() {
LocalPackage localPackage = getInstantAppLocalPackage();
if (localPackage == null) {
return ensureSdkInstalled().getLocation();
}
return localPackage.getLocation();
}
@Nullable
private static LocalPackage getInstantAppLocalPackage() {
AndroidSdkHandler androidSdkHandler = AndroidSdks.getInstance().tryToChooseSdkHandler();
return androidSdkHandler.getLocalPackage(INSTANT_APP_SDK_PATH, new StudioLoggerProgressIndicator(InstantAppSdks.class));
}
private static @NotNull LocalPackage ensureSdkInstalled() {
ApplicationManager.getApplication().invokeAndWait(() -> {
int result = Messages.showYesNoDialog(
"Required Google Play Instant SDK not installed. Do you want to install it now?", "Google Play Instant", null);
if (result == Messages.OK) {
ModelWizardDialog dialog = createDialogForPaths(null, ImmutableList.of(INSTANT_APP_SDK_PATH));
if (dialog != null) {
dialog.show();
}
}
});
LocalPackage localPackage = getInstantAppLocalPackage();
if (localPackage == null) {
throw new LoadInstantAppSdkException("Could not load the Google Play Instant SDK");
} else {
return localPackage;
}
}
private static void updateSdk() {
ApplicationManager.getApplication().invokeAndWait(() -> {
int result = Messages.showYesNoDialog(UPGRADE_PROMPT_TEXT, "Google Play Instant", null);
if (result == Messages.OK) {
ModelWizardDialog dialog = createDialogForPaths(null, ImmutableList.of(INSTANT_APP_SDK_PATH));
if (dialog != null) {
dialog.show();
}
}
});
}
/**
* Attempts to dynamically load the Instant Apps SDK library used to provision devices and run
* apps. Returns null if it could not be loaded.
*/
@NotNull
public ExtendedSdk loadLibrary() {
return loadLibrary(true);
}
@NotNull
@VisibleForTesting
ExtendedSdk loadLibrary(boolean attemptUpgrades) {
if (cachedSdkLib == null) {
File sdkRoot = getOrInstallInstantAppSdk();
File jar = sdkRoot.toPath().resolve(SDK_LIB_JAR_PATH).toFile();
if (!jar.exists()) {
// This SDK is too old and is lacking the library JAR
if (attemptUpgrades) {
updateSdk();
return loadLibrary(false);
}
else {
throw COULD_NOT_LOAD_NEW_SDK_EXCEPTION;
}
}
cachedSdkLib = new SdkLoader().loadSdk(
jar,
TelemetryManager.HostApplication.ANDROID_STUDIO, ApplicationInfo.getInstance().getFullVersion());
if (cachedSdkLib == null) {
if (attemptUpgrades) {
// This SDK contains a library JAR that's too old
updateSdk();
return loadLibrary(false);
}
else {
throw COULD_NOT_LOAD_NEW_SDK_EXCEPTION;
}
}
}
// We want to set this every time the SDK is used to keep it up to date
cachedSdkLib.getTelemetryManager().setOptInStatus(AnalyticsSettings.getOptedIn()
? TelemetryManager.OptInStatus.OPTED_IN
: TelemetryManager.OptInStatus.OPTED_OUT);
return cachedSdkLib;
}
public static class LoadInstantAppSdkException extends RuntimeException {
public LoadInstantAppSdkException(@NotNull String message) {
super(message);
}
}
}