| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net; |
| |
| import android.content.Context; |
| import android.util.Log; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Provides a factory method to create {@link CronetEngine.Builder} instances. A {@code |
| * CronetEngine.Builder} instance can be used to create a specific {@link CronetEngine} |
| * implementation. To get the list of available {@link CronetProvider}s call {@link |
| * #getAllProviders(Context)}. |
| * <p/> |
| * <b>NOTE:</b> This class is for advanced users that want to select a particular |
| * Cronet implementation. Most users should simply use {@code new} {@link |
| * CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}. |
| * |
| * {@hide} |
| */ |
| public abstract class CronetProvider { |
| /** |
| * String returned by {@link CronetProvider#getName} for {@link CronetProvider} that provides |
| * native Cronet implementation packaged inside an application. This implementation offers |
| * significantly higher performance relative to the fallback Cronet implementations (see {@link |
| * #PROVIDER_NAME_FALLBACK}). |
| */ |
| public static final String PROVIDER_NAME_APP_PACKAGED = "App-Packaged-Cronet-Provider"; |
| |
| /** |
| * String returned by {@link CronetProvider#getName} for {@link CronetProvider} that provides |
| * Cronet implementation based on the system's {@link java.net.HttpURLConnection} |
| * implementation. This implementation offers significantly degraded performance relative to |
| * native Cronet implementations (see {@link #PROVIDER_NAME_APP_PACKAGED}). |
| */ |
| public static final String PROVIDER_NAME_FALLBACK = "Fallback-Cronet-Provider"; |
| |
| /** |
| * The name of an optional key in the app string resource file that contains the class name of |
| * an alternative {@code CronetProvider} implementation. |
| */ |
| private static final String RES_KEY_CRONET_IMPL_CLASS = "CronetProviderClassName"; |
| |
| private static final String TAG = CronetProvider.class.getSimpleName(); |
| |
| protected final Context mContext; |
| |
| protected CronetProvider(Context context) { |
| if (context == null) { |
| throw new IllegalArgumentException("Context must not be null"); |
| } |
| mContext = context; |
| } |
| |
| /** |
| * Creates and returns an instance of {@link CronetEngine.Builder}. |
| * <p/> |
| * <b>NOTE:</b> This class is for advanced users that want to select a particular |
| * Cronet implementation. Most users should simply use {@code new} {@link |
| * CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}. |
| * |
| * @return {@code CronetEngine.Builder}. |
| * @throws IllegalStateException if the provider is not enabled (see {@link #isEnabled}. |
| */ |
| public abstract CronetEngine.Builder createBuilder(); |
| |
| /** |
| * Returns the provider name. The well-know provider names include: |
| * <ul> |
| * <li>{@link #PROVIDER_NAME_APP_PACKAGED}</li> |
| * <li>{@link #PROVIDER_NAME_FALLBACK}</li> |
| * </ul> |
| * |
| * @return provider name. |
| */ |
| public abstract String getName(); |
| |
| /** |
| * Returns the provider version. The version can be used to select the newest available provider |
| * if multiple providers are available. |
| * |
| * @return provider version. |
| */ |
| public abstract String getVersion(); |
| |
| /** |
| * Returns whether the provider is enabled and can be used to instantiate the Cronet engine. A |
| * provider being out-of-date (older than the API) and needing updating is one potential reason |
| * it could be disabled. Please read the provider documentation for enablement procedure. |
| * |
| * @return {@code true} if the provider is enabled. |
| */ |
| public abstract boolean isEnabled(); |
| |
| @Override |
| public String toString() { |
| return "[" |
| + "class=" |
| + getClass().getName() |
| + ", " |
| + "name=" |
| + getName() |
| + ", " |
| + "version=" |
| + getVersion() |
| + ", " |
| + "enabled=" |
| + isEnabled() |
| + "]"; |
| } |
| |
| /** Name of the HttpEngine Native {@link CronetProvider} class. */ |
| private static final String HTTPENGINE_NATIVE_PROVIDER_CLASS = |
| "org.chromium.net.impl.HttpEngineNativeProvider"; |
| |
| /** Name of the Java {@link CronetProvider} class. */ |
| private static final String JAVA_CRONET_PROVIDER_CLASS = |
| "org.chromium.net.impl.JavaCronetProvider"; |
| |
| /** Name of the native {@link CronetProvider} class. */ |
| private static final String NATIVE_CRONET_PROVIDER_CLASS = |
| "org.chromium.net.impl.NativeCronetProvider"; |
| |
| /** {@link CronetProvider} class that is packaged with Google Play Services. */ |
| private static final String PLAY_SERVICES_CRONET_PROVIDER_CLASS = |
| "com.google.android.gms.net.PlayServicesCronetProvider"; |
| |
| /** |
| * {@link CronetProvider} a deprecated class that may be packaged with some old versions of |
| * Google Play Services. |
| */ |
| private static final String GMS_CORE_CRONET_PROVIDER_CLASS = |
| "com.google.android.gms.net.GmsCoreCronetProvider"; |
| |
| /** |
| * Returns an unmodifiable list of all available {@link CronetProvider}s. The providers are |
| * returned in no particular order. Some of the returned providers may be in a disabled state |
| * and should be enabled by the invoker. See {@link CronetProvider#isEnabled()}. |
| * |
| * @return the list of available providers. |
| */ |
| public static List<CronetProvider> getAllProviders(Context context) { |
| // Use LinkedHashSet to preserve the order and eliminate duplicate providers. |
| Set<CronetProvider> providers = new LinkedHashSet<>(); |
| addCronetProviderFromResourceFile(context, providers); |
| addCronetProviderImplByClassName( |
| context, PLAY_SERVICES_CRONET_PROVIDER_CLASS, providers, false); |
| addCronetProviderImplByClassName(context, GMS_CORE_CRONET_PROVIDER_CLASS, providers, false); |
| addCronetProviderImplByClassName(context, NATIVE_CRONET_PROVIDER_CLASS, providers, false); |
| addCronetProviderImplByClassName( |
| context, HTTPENGINE_NATIVE_PROVIDER_CLASS, providers, false); |
| addCronetProviderImplByClassName(context, JAVA_CRONET_PROVIDER_CLASS, providers, false); |
| return Collections.unmodifiableList(new ArrayList<>(providers)); |
| } |
| |
| /** |
| * Attempts to add a new provider referenced by the class name to a set. |
| * |
| * @param className the class name of the provider that should be instantiated. |
| * @param providers the set of providers to add the new provider to. |
| * @return {@code true} if the provider was added to the set; {@code false} if the provider |
| * couldn't be instantiated. |
| */ |
| private static boolean addCronetProviderImplByClassName( |
| Context context, String className, Set<CronetProvider> providers, boolean logError) { |
| ClassLoader loader = context.getClassLoader(); |
| try { |
| Class<? extends CronetProvider> providerClass = |
| loader.loadClass(className).asSubclass(CronetProvider.class); |
| Constructor<? extends CronetProvider> ctor = |
| providerClass.getConstructor(Context.class); |
| providers.add(ctor.newInstance(context)); |
| return true; |
| } catch (InstantiationException e) { |
| logReflectiveOperationException(className, logError, e); |
| } catch (InvocationTargetException e) { |
| logReflectiveOperationException(className, logError, e); |
| } catch (NoSuchMethodException e) { |
| logReflectiveOperationException(className, logError, e); |
| } catch (IllegalAccessException e) { |
| logReflectiveOperationException(className, logError, e); |
| } catch (ClassNotFoundException e) { |
| logReflectiveOperationException(className, logError, e); |
| } |
| return false; |
| } |
| |
| /** |
| * De-duplicates exception handling logic in {@link #addCronetProviderImplByClassName}. It |
| * should be removed when support of API Levels lower than 19 is deprecated. |
| */ |
| private static void logReflectiveOperationException( |
| String className, boolean logError, Exception e) { |
| if (logError) { |
| Log.e(TAG, "Unable to load provider class: " + className, e); |
| } else { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d( |
| TAG, |
| "Tried to load " |
| + className |
| + " provider class but it wasn't" |
| + " included in the app classpath"); |
| } |
| } |
| } |
| |
| /** |
| * Attempts to add a provider specified in the app resource file to a set. |
| * |
| * @param providers the set of providers to add the new provider to. |
| * @return {@code true} if the provider was added to the set; {@code false} if the app resources |
| * do not include the string with {@link #RES_KEY_CRONET_IMPL_CLASS} key. |
| * @throws RuntimeException if the provider cannot be found or instantiated. |
| */ |
| // looking up resources from other apps requires the use of getIdentifier() |
| @SuppressWarnings("DiscouragedApi") |
| private static boolean addCronetProviderFromResourceFile( |
| Context context, Set<CronetProvider> providers) { |
| int resId = |
| context.getResources() |
| .getIdentifier( |
| RES_KEY_CRONET_IMPL_CLASS, "string", context.getPackageName()); |
| // Resource not found |
| if (resId == 0) { |
| // The resource wasn't included in the app; therefore, there is nothing to add. |
| return false; |
| } |
| String className = context.getResources().getString(resId); |
| |
| // If the resource specifies a well known provider, don't load it because |
| // there will be an attempt to load it anyways. |
| if (className == null |
| || className.equals(PLAY_SERVICES_CRONET_PROVIDER_CLASS) |
| || className.equals(GMS_CORE_CRONET_PROVIDER_CLASS) |
| || className.equals(JAVA_CRONET_PROVIDER_CLASS) |
| || className.equals(NATIVE_CRONET_PROVIDER_CLASS)) { |
| return false; |
| } |
| |
| if (!addCronetProviderImplByClassName(context, className, providers, true)) { |
| Log.e( |
| TAG, |
| "Unable to instantiate Cronet implementation class " |
| + className |
| + " that is listed as in the app string resource file under " |
| + RES_KEY_CRONET_IMPL_CLASS |
| + " key"); |
| } |
| return true; |
| } |
| } |