• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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