1 // Copyright 2017 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import android.content.Context; 8 import android.util.Log; 9 10 import java.lang.reflect.Constructor; 11 import java.lang.reflect.InvocationTargetException; 12 import java.util.ArrayList; 13 import java.util.Collections; 14 import java.util.LinkedHashSet; 15 import java.util.List; 16 import java.util.Set; 17 18 /** 19 * Provides a factory method to create {@link CronetEngine.Builder} instances. A {@code 20 * CronetEngine.Builder} instance can be used to create a specific {@link CronetEngine} 21 * implementation. To get the list of available {@link CronetProvider}s call {@link 22 * #getAllProviders(Context)}. 23 * <p/> 24 * <b>NOTE:</b> This class is for advanced users that want to select a particular 25 * Cronet implementation. Most users should simply use {@code new} {@link 26 * CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}. 27 * 28 * {@hide} 29 */ 30 public abstract class CronetProvider { 31 /** 32 * String returned by {@link CronetProvider#getName} for {@link CronetProvider} that provides 33 * native Cronet implementation packaged inside an application. This implementation offers 34 * significantly higher performance relative to the fallback Cronet implementations (see {@link 35 * #PROVIDER_NAME_FALLBACK}). 36 */ 37 public static final String PROVIDER_NAME_APP_PACKAGED = "App-Packaged-Cronet-Provider"; 38 39 /** 40 * String returned by {@link CronetProvider#getName} for {@link CronetProvider} that provides 41 * Cronet implementation based on the system's {@link java.net.HttpURLConnection} 42 * implementation. This implementation offers significantly degraded performance relative to 43 * native Cronet implementations (see {@link #PROVIDER_NAME_APP_PACKAGED}). 44 */ 45 public static final String PROVIDER_NAME_FALLBACK = "Fallback-Cronet-Provider"; 46 47 /** 48 * The name of an optional key in the app string resource file that contains the class name of 49 * an alternative {@code CronetProvider} implementation. 50 */ 51 private static final String RES_KEY_CRONET_IMPL_CLASS = "CronetProviderClassName"; 52 53 private static final String TAG = CronetProvider.class.getSimpleName(); 54 55 protected final Context mContext; 56 CronetProvider(Context context)57 protected CronetProvider(Context context) { 58 if (context == null) { 59 throw new IllegalArgumentException("Context must not be null"); 60 } 61 mContext = context; 62 } 63 64 /** 65 * Creates and returns an instance of {@link CronetEngine.Builder}. 66 * <p/> 67 * <b>NOTE:</b> This class is for advanced users that want to select a particular 68 * Cronet implementation. Most users should simply use {@code new} {@link 69 * CronetEngine.Builder#CronetEngine.Builder(android.content.Context)}. 70 * 71 * @return {@code CronetEngine.Builder}. 72 * @throws IllegalStateException if the provider is not enabled (see {@link #isEnabled}. 73 */ createBuilder()74 public abstract CronetEngine.Builder createBuilder(); 75 76 /** 77 * Returns the provider name. The well-know provider names include: 78 * <ul> 79 * <li>{@link #PROVIDER_NAME_APP_PACKAGED}</li> 80 * <li>{@link #PROVIDER_NAME_FALLBACK}</li> 81 * </ul> 82 * 83 * @return provider name. 84 */ getName()85 public abstract String getName(); 86 87 /** 88 * Returns the provider version. The version can be used to select the newest available provider 89 * if multiple providers are available. 90 * 91 * @return provider version. 92 */ getVersion()93 public abstract String getVersion(); 94 95 /** 96 * Returns whether the provider is enabled and can be used to instantiate the Cronet engine. A 97 * provider being out-of-date (older than the API) and needing updating is one potential reason 98 * it could be disabled. Please read the provider documentation for enablement procedure. 99 * 100 * @return {@code true} if the provider is enabled. 101 */ isEnabled()102 public abstract boolean isEnabled(); 103 104 @Override toString()105 public String toString() { 106 return "[" 107 + "class=" 108 + getClass().getName() 109 + ", " 110 + "name=" 111 + getName() 112 + ", " 113 + "version=" 114 + getVersion() 115 + ", " 116 + "enabled=" 117 + isEnabled() 118 + "]"; 119 } 120 121 /** Name of the HttpEngine Native {@link CronetProvider} class. */ 122 private static final String HTTPENGINE_NATIVE_PROVIDER_CLASS = 123 "org.chromium.net.impl.HttpEngineNativeProvider"; 124 125 /** Name of the Java {@link CronetProvider} class. */ 126 private static final String JAVA_CRONET_PROVIDER_CLASS = 127 "org.chromium.net.impl.JavaCronetProvider"; 128 129 /** Name of the native {@link CronetProvider} class. */ 130 private static final String NATIVE_CRONET_PROVIDER_CLASS = 131 "org.chromium.net.impl.NativeCronetProvider"; 132 133 /** {@link CronetProvider} class that is packaged with Google Play Services. */ 134 private static final String PLAY_SERVICES_CRONET_PROVIDER_CLASS = 135 "com.google.android.gms.net.PlayServicesCronetProvider"; 136 137 /** 138 * {@link CronetProvider} a deprecated class that may be packaged with some old versions of 139 * Google Play Services. 140 */ 141 private static final String GMS_CORE_CRONET_PROVIDER_CLASS = 142 "com.google.android.gms.net.GmsCoreCronetProvider"; 143 144 /** 145 * Returns an unmodifiable list of all available {@link CronetProvider}s. The providers are 146 * returned in no particular order. Some of the returned providers may be in a disabled state 147 * and should be enabled by the invoker. See {@link CronetProvider#isEnabled()}. 148 * 149 * @return the list of available providers. 150 */ getAllProviders(Context context)151 public static List<CronetProvider> getAllProviders(Context context) { 152 // Use LinkedHashSet to preserve the order and eliminate duplicate providers. 153 Set<CronetProvider> providers = new LinkedHashSet<>(); 154 addCronetProviderFromResourceFile(context, providers); 155 addCronetProviderImplByClassName( 156 context, PLAY_SERVICES_CRONET_PROVIDER_CLASS, providers, false); 157 addCronetProviderImplByClassName(context, GMS_CORE_CRONET_PROVIDER_CLASS, providers, false); 158 addCronetProviderImplByClassName(context, NATIVE_CRONET_PROVIDER_CLASS, providers, false); 159 addCronetProviderImplByClassName( 160 context, HTTPENGINE_NATIVE_PROVIDER_CLASS, providers, false); 161 addCronetProviderImplByClassName(context, JAVA_CRONET_PROVIDER_CLASS, providers, false); 162 return Collections.unmodifiableList(new ArrayList<>(providers)); 163 } 164 165 /** 166 * Attempts to add a new provider referenced by the class name to a set. 167 * 168 * @param className the class name of the provider that should be instantiated. 169 * @param providers the set of providers to add the new provider to. 170 * @return {@code true} if the provider was added to the set; {@code false} if the provider 171 * couldn't be instantiated. 172 */ addCronetProviderImplByClassName( Context context, String className, Set<CronetProvider> providers, boolean logError)173 private static boolean addCronetProviderImplByClassName( 174 Context context, String className, Set<CronetProvider> providers, boolean logError) { 175 ClassLoader loader = context.getClassLoader(); 176 try { 177 Class<? extends CronetProvider> providerClass = 178 loader.loadClass(className).asSubclass(CronetProvider.class); 179 Constructor<? extends CronetProvider> ctor = 180 providerClass.getConstructor(Context.class); 181 providers.add(ctor.newInstance(context)); 182 return true; 183 } catch (InstantiationException e) { 184 logReflectiveOperationException(className, logError, e); 185 } catch (InvocationTargetException e) { 186 logReflectiveOperationException(className, logError, e); 187 } catch (NoSuchMethodException e) { 188 logReflectiveOperationException(className, logError, e); 189 } catch (IllegalAccessException e) { 190 logReflectiveOperationException(className, logError, e); 191 } catch (ClassNotFoundException e) { 192 logReflectiveOperationException(className, logError, e); 193 } 194 return false; 195 } 196 197 /** 198 * De-duplicates exception handling logic in {@link #addCronetProviderImplByClassName}. It 199 * should be removed when support of API Levels lower than 19 is deprecated. 200 */ logReflectiveOperationException( String className, boolean logError, Exception e)201 private static void logReflectiveOperationException( 202 String className, boolean logError, Exception e) { 203 if (logError) { 204 Log.e(TAG, "Unable to load provider class: " + className, e); 205 } else { 206 if (Log.isLoggable(TAG, Log.DEBUG)) { 207 Log.d( 208 TAG, 209 "Tried to load " 210 + className 211 + " provider class but it wasn't" 212 + " included in the app classpath"); 213 } 214 } 215 } 216 217 /** 218 * Attempts to add a provider specified in the app resource file to a set. 219 * 220 * @param providers the set of providers to add the new provider to. 221 * @return {@code true} if the provider was added to the set; {@code false} if the app resources 222 * do not include the string with {@link #RES_KEY_CRONET_IMPL_CLASS} key. 223 * @throws RuntimeException if the provider cannot be found or instantiated. 224 */ 225 // looking up resources from other apps requires the use of getIdentifier() 226 @SuppressWarnings("DiscouragedApi") addCronetProviderFromResourceFile( Context context, Set<CronetProvider> providers)227 private static boolean addCronetProviderFromResourceFile( 228 Context context, Set<CronetProvider> providers) { 229 int resId = 230 context.getResources() 231 .getIdentifier( 232 RES_KEY_CRONET_IMPL_CLASS, "string", context.getPackageName()); 233 // Resource not found 234 if (resId == 0) { 235 // The resource wasn't included in the app; therefore, there is nothing to add. 236 return false; 237 } 238 String className = context.getResources().getString(resId); 239 240 // If the resource specifies a well known provider, don't load it because 241 // there will be an attempt to load it anyways. 242 if (className == null 243 || className.equals(PLAY_SERVICES_CRONET_PROVIDER_CLASS) 244 || className.equals(GMS_CORE_CRONET_PROVIDER_CLASS) 245 || className.equals(JAVA_CRONET_PROVIDER_CLASS) 246 || className.equals(NATIVE_CRONET_PROVIDER_CLASS)) { 247 return false; 248 } 249 250 if (!addCronetProviderImplByClassName(context, className, providers, true)) { 251 Log.e( 252 TAG, 253 "Unable to instantiate Cronet implementation class " 254 + className 255 + " that is listed as in the app string resource file under " 256 + RES_KEY_CRONET_IMPL_CLASS 257 + " key"); 258 } 259 return true; 260 } 261 } 262