| /* |
| * 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); |
| } |
| } |
| } |