| /* |
| * 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.run; |
| |
| import static com.intellij.util.ThreeState.NO; |
| |
| import com.android.annotations.concurrency.Slow; |
| import com.android.ddmlib.Client; |
| import com.android.ddmlib.IDevice; |
| import com.android.sdklib.AndroidVersion; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.tools.idea.gradle.project.model.AndroidModuleModel; |
| import com.android.tools.idea.model.AndroidModuleInfo; |
| import com.android.tools.idea.run.util.LaunchUtils; |
| import com.android.tools.idea.run.util.SwapInfo; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.intellij.execution.runners.ExecutionEnvironment; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.android.sdk.AndroidPlatform; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| public class LaunchCompatibilityCheckerImpl implements LaunchCompatibilityChecker { |
| @NotNull @VisibleForTesting final AndroidVersion myMinSdkVersion; |
| @NotNull private final IAndroidTarget myProjectTarget; |
| @NotNull private final AndroidFacet myFacet; |
| @Nullable private final ExecutionEnvironment myEnvironment; |
| @Nullable private final AndroidRunConfigurationBase myAndroidRunConfigurationBase; |
| @NotNull private final Set<String> mySupportedAbis; |
| |
| public LaunchCompatibilityCheckerImpl(@NotNull AndroidVersion minSdkVersion, |
| @NotNull IAndroidTarget target, |
| @NotNull AndroidFacet facet, |
| @Nullable ExecutionEnvironment environment, |
| @Nullable AndroidRunConfigurationBase androidRunConfigurationBase, |
| @NotNull Set<String> supportedAbis) { |
| assert (environment == null && androidRunConfigurationBase == null) || (environment != null && androidRunConfigurationBase != null); |
| myMinSdkVersion = minSdkVersion; |
| myProjectTarget = target; |
| myEnvironment = environment; |
| myAndroidRunConfigurationBase = androidRunConfigurationBase; |
| myFacet = facet; |
| mySupportedAbis = supportedAbis; |
| } |
| |
| /** |
| * Validates the given {@link AndroidDevice} and returns the {@link LaunchCompatibility}. This method |
| * can block while obtaining the hardware features from the {@link AndroidFacet}. |
| */ |
| @Slow |
| @NotNull |
| @Override |
| public LaunchCompatibility validate(@NotNull AndroidDevice device) { |
| LaunchCompatibility launchCompatibility = LaunchCompatibility.YES; |
| |
| if (myEnvironment != null && myAndroidRunConfigurationBase != null) { |
| SwapInfo swapInfo = myEnvironment.getUserData(SwapInfo.SWAP_INFO_KEY); |
| if (swapInfo != null) { |
| if (device.getVersion().compareTo(AndroidVersion.VersionCodes.O, null) < 0) { |
| launchCompatibility = new LaunchCompatibility(NO, "The device needs to be running Oreo or newer."); |
| } |
| else if (!device.isRunning()) { |
| launchCompatibility = new LaunchCompatibility(NO, "Please ensure the target device/emulator is running."); |
| } |
| else { |
| try { |
| ApplicationIdProvider applicationIdProvider = myAndroidRunConfigurationBase.getApplicationIdProvider(myFacet); |
| Client client = device.getLaunchedDevice().get().getClient(applicationIdProvider.getPackageName()); |
| if (client == null) { |
| launchCompatibility = new LaunchCompatibility( |
| NO, "App not running on device. Please first install/run the app on the target device/emulator."); |
| } |
| } |
| catch (InterruptedException | ExecutionException | ApkProvisionException e) { |
| launchCompatibility = new LaunchCompatibility(NO, "Could not determine if device is compatible."); |
| } |
| } |
| } |
| } |
| |
| return launchCompatibility.combine(device.canRun(myMinSdkVersion, myProjectTarget, myFacet, |
| LaunchCompatibilityCheckerImpl::getRequiredHardwareFeatures, mySupportedAbis)); |
| } |
| |
| /** |
| * Returns the required hardware features from a given {@link AndroidFacet}. |
| */ |
| @NotNull |
| static EnumSet<IDevice.HardwareFeature> getRequiredHardwareFeatures(@NotNull AndroidFacet facet) { |
| // Currently, we only look at whether the device supports the watch feature. |
| // We may not want to search the device for every possible feature, but only a small subset of important |
| // features, starting with hardware type watch. |
| if (LaunchUtils.isWatchFeatureRequired(facet)) { |
| return EnumSet.of(IDevice.HardwareFeature.WATCH); |
| } |
| else { |
| return EnumSet.noneOf(IDevice.HardwareFeature.class); |
| } |
| } |
| |
| public static LaunchCompatibilityChecker create(@NotNull AndroidFacet facet, |
| @Nullable ExecutionEnvironment env, |
| @Nullable AndroidRunConfigurationBase androidRunConfigurationBase) { |
| AndroidVersion minSdkVersion = getMinSdkVersion(facet); |
| AndroidPlatform platform = AndroidPlatform.getInstance(facet.getModule()); |
| if (platform == null) { |
| throw new IllegalStateException("Android platform not set for module: " + facet.getModule().getName()); |
| } |
| AndroidModuleModel androidModuleModel = AndroidModuleModel.get(facet); |
| Set<String> supportedAbis = androidModuleModel != null ? |
| androidModuleModel.getSelectedVariant().getMainArtifact().getAbiFilters() : |
| Collections.emptySet(); |
| return new LaunchCompatibilityCheckerImpl(minSdkVersion, platform.getTarget(), facet, env, androidRunConfigurationBase, supportedAbis); |
| } |
| |
| private static AndroidVersion getMinSdkVersion(@NotNull AndroidFacet facet) { |
| ListenableFuture<AndroidVersion> minSdkVersionFuture = AndroidModuleInfo.getInstance(facet).getRuntimeMinSdkVersion(); |
| if (minSdkVersionFuture.isDone()) { |
| try { |
| return minSdkVersionFuture.get(); |
| } |
| catch (ExecutionException|InterruptedException ignored) { |
| // It'd be nice to log something here, but we're constantly validating |
| // launch compatibility so that would result in a lot of spam. |
| } |
| } |
| return AndroidVersion.DEFAULT; |
| } |
| } |