1package com.android.webview.chromium;
2
3import android.app.Application;
4import android.content.Context;
5import android.content.pm.PackageInfo;
6import android.content.res.AssetManager;
7import android.content.res.Resources;
8import android.graphics.Canvas;
9import android.os.Trace;
10import android.util.Log;
11import android.util.SparseArray;
12import android.view.View;
13import android.webkit.WebViewFactory;
14
15import java.lang.reflect.InvocationTargetException;
16import java.lang.reflect.Method;
17
18/**
19 * Factory class for {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate}s.
20 *
21 * <p>{@link WebViewDelegate com.android.webview.chromium.WebViewDelegate}s provide the same
22 * interface as {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} but without
23 * a dependency on the webkit class. Defining our own
24 * {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} in frameworks/webview
25 * allows the WebView apk to be binary compatible with the API 21 version of the framework, in
26 * which {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been
27 * introduced.
28 *
29 * <p>The {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} interface and this
30 * factory class can be removed once we don't longer need to support WebView apk updates to devices
31 * running the API 21 version of the framework. At that point, we should use
32 * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} directly instead.
33 */
34class WebViewDelegateFactory {
35
36    /**
37     * Copy of {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}'s interface.
38     * See {@link WebViewDelegateFactory} for the reasons why this copy is needed.
39     */
40    interface WebViewDelegate {
41        /** @see android.webkit.WebViewDelegate.OnTraceEnabledChangeListener */
42        interface OnTraceEnabledChangeListener {
43            void onTraceEnabledChange(boolean enabled);
44        }
45
46        /** @see android.webkit.WebViewDelegate#setOnTraceEnabledChangeListener */
47        void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener);
48
49        /** @see android.webkit.WebViewDelegate#isTraceTagEnabled */
50        boolean isTraceTagEnabled();
51
52        /** @see android.webkit.WebViewDelegate#canInvokeDrawGlFunctor */
53        boolean canInvokeDrawGlFunctor(View containerView);
54
55        /** @see android.webkit.WebViewDelegate#invokeDrawGlFunctor */
56        void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor,
57                boolean waitForCompletion);
58
59        /** @see android.webkit.WebViewDelegate#callDrawGlFunction */
60        void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor);
61
62        /** @see android.webkit.WebViewDelegate#detachDrawGlFunctor */
63        void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor);
64
65        /** @see android.webkit.WebViewDelegate#getPackageId */
66        int getPackageId(Resources resources, String packageName);
67
68        /** @see android.webkit.WebViewDelegate#getApplication */
69        Application getApplication();
70
71        /** @see android.webkit.WebViewDelegate#getErrorString */
72        String getErrorString(Context context, int errorCode);
73
74        /** @see android.webkit.WebViewDelegate#addWebViewAssetPath */
75        void addWebViewAssetPath(Context context);
76    }
77
78    /**
79     * Creates a {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} that proxies
80     * requests to the given {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}.
81     *
82     * @return the created delegate
83     */
84    static WebViewDelegate createProxyDelegate(android.webkit.WebViewDelegate delegate) {
85        return new ProxyDelegate(delegate);
86    }
87
88    /**
89     * Creates a {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} compatible
90     * with the API 21 version of the framework in which
91     * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been
92     * introduced.
93     *
94     * @return the created delegate
95     */
96    static WebViewDelegate createApi21CompatibilityDelegate() {
97        return new Api21CompatibilityDelegate();
98    }
99
100    /**
101     * A {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} that proxies requests
102     * to a {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}.
103     */
104    private static class ProxyDelegate implements WebViewDelegate {
105
106        android.webkit.WebViewDelegate delegate;
107
108        ProxyDelegate(android.webkit.WebViewDelegate delegate) {
109            this.delegate = delegate;
110        }
111
112        @Override
113        public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) {
114            delegate.setOnTraceEnabledChangeListener(
115                    new android.webkit.WebViewDelegate.OnTraceEnabledChangeListener() {
116                        @Override
117                        public void onTraceEnabledChange(boolean enabled) {
118                            listener.onTraceEnabledChange(enabled);
119                            ;
120                        }
121                    });
122        }
123
124        @Override
125        public boolean isTraceTagEnabled() {
126            return delegate.isTraceTagEnabled();
127        }
128
129        @Override
130        public boolean canInvokeDrawGlFunctor(View containerView) {
131            return delegate.canInvokeDrawGlFunctor(containerView);
132        }
133
134        @Override
135        public void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor,
136                boolean waitForCompletion) {
137            delegate.invokeDrawGlFunctor(containerView, nativeDrawGLFunctor, waitForCompletion);
138        }
139
140        @Override
141        public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) {
142            delegate.callDrawGlFunction(canvas, nativeDrawGLFunctor);
143        }
144
145        @Override
146        public void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor) {
147            delegate.detachDrawGlFunctor(containerView, nativeDrawGLFunctor);
148        }
149
150        @Override
151        public int getPackageId(Resources resources, String packageName) {
152            return delegate.getPackageId(resources, packageName);
153        }
154
155        @Override
156        public Application getApplication() {
157            return delegate.getApplication();
158        }
159
160        @Override
161        public String getErrorString(Context context, int errorCode) {
162            return delegate.getErrorString(context, errorCode);
163        }
164
165        @Override
166        public void addWebViewAssetPath(Context context) {
167            delegate.addWebViewAssetPath(context);
168        }
169    }
170
171    /**
172     * A {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} compatible with the
173     * API 21 version of the framework in which
174     * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been
175     * introduced.
176     *
177     * <p>This class implements the
178     * {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} functionality by using
179     * reflection to call into hidden frameworks APIs released in the API-21 version of the
180     * framework.
181     */
182    private static class Api21CompatibilityDelegate implements WebViewDelegate {
183        /** Copy of Trace.TRACE_TAG_WEBVIEW */
184        private final static long TRACE_TAG_WEBVIEW = 1L << 4;
185
186        /** Hidden APIs released in the API 21 version of the framework */
187        private final Method mIsTagEnabledMethod;
188        private final Method mAddChangeCallbackMethod;
189        private final Method mGetViewRootImplMethod;
190        private final Method mInvokeFunctorMethod;
191        private final Method mCallDrawGLFunctionMethod;
192        private final Method mDetachFunctorMethod;
193        private final Method mGetAssignedPackageIdentifiersMethod;
194        private final Method mAddAssetPathMethod;
195        private final Method mCurrentApplicationMethod;
196        private final Method mGetStringMethod;
197        private final Method mGetLoadedPackageInfoMethod;
198
199        Api21CompatibilityDelegate() {
200            try {
201                // Important: This reflection essentially defines a snapshot of some hidden APIs
202                // at version 21 of the framework for compatibility reasons, and the reflection
203                // should not be changed even if those hidden APIs change in future releases.
204                mIsTagEnabledMethod = Trace.class.getMethod("isTagEnabled", long.class);
205                mAddChangeCallbackMethod = Class.forName("android.os.SystemProperties")
206                        .getMethod("addChangeCallback", Runnable.class);
207                mGetViewRootImplMethod = View.class.getMethod("getViewRootImpl");
208                mInvokeFunctorMethod = Class.forName("android.view.ViewRootImpl")
209                        .getMethod("invokeFunctor", long.class, boolean.class);
210                mDetachFunctorMethod = Class.forName("android.view.ViewRootImpl")
211                        .getMethod("detachFunctor", long.class);
212                mCallDrawGLFunctionMethod = Class.forName("android.view.HardwareCanvas")
213                        .getMethod("callDrawGLFunction", long.class);
214                mGetAssignedPackageIdentifiersMethod = AssetManager.class.getMethod(
215                        "getAssignedPackageIdentifiers");
216                mAddAssetPathMethod = AssetManager.class.getMethod(
217                        "addAssetPath", String.class);
218                mCurrentApplicationMethod = Class.forName("android.app.ActivityThread")
219                        .getMethod("currentApplication");
220                mGetStringMethod = Class.forName("android.net.http.ErrorStrings")
221                        .getMethod("getString", int.class, Context.class);
222                mGetLoadedPackageInfoMethod = Class.forName("android.webkit.WebViewFactory")
223                        .getMethod("getLoadedPackageInfo");
224            } catch (Exception e) {
225                throw new RuntimeException("Invalid reflection", e);
226            }
227        }
228
229        @Override
230        public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) {
231            try {
232                mAddChangeCallbackMethod.invoke(null, new Runnable() {
233                    @Override
234                    public void run() {
235                        listener.onTraceEnabledChange(isTraceTagEnabled());
236                    }
237                });
238            } catch (Exception e) {
239                throw new RuntimeException("Invalid reflection", e);
240            }
241        }
242
243        @Override
244        public boolean isTraceTagEnabled() {
245            try {
246                return ((Boolean) mIsTagEnabledMethod.invoke(null, TRACE_TAG_WEBVIEW));
247            } catch (Exception e) {
248                throw new RuntimeException("Invalid reflection", e);
249            }
250        }
251
252        @Override
253        public boolean canInvokeDrawGlFunctor(View containerView) {
254            try {
255                Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView);
256                 // viewRootImpl can be null during teardown when window is leaked.
257                return viewRootImpl != null;
258            } catch (Exception e) {
259                throw new RuntimeException("Invalid reflection", e);
260            }
261        }
262
263        @Override
264        public void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor,
265                boolean waitForCompletion) {
266            try {
267                Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView);
268                if (viewRootImpl != null) {
269                    mInvokeFunctorMethod.invoke(viewRootImpl, nativeDrawGLFunctor, waitForCompletion);
270                }
271            } catch (Exception e) {
272                throw new RuntimeException("Invalid reflection", e);
273            }
274        }
275
276        @Override
277        public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) {
278            try {
279                mCallDrawGLFunctionMethod.invoke(canvas, nativeDrawGLFunctor);
280            } catch (Exception e) {
281                throw new RuntimeException("Invalid reflection", e);
282            }
283        }
284
285        @Override
286        public void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor) {
287            try {
288                Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView);
289                if (viewRootImpl != null) {
290                    mDetachFunctorMethod.invoke(viewRootImpl, nativeDrawGLFunctor);
291                }
292            } catch (Exception e) {
293                throw new RuntimeException("Invalid reflection", e);
294            }
295        }
296
297        @Override
298        public int getPackageId(Resources resources, String packageName) {
299            try {
300                SparseArray packageIdentifiers =
301                        (SparseArray) mGetAssignedPackageIdentifiersMethod.invoke(
302                                resources.getAssets());
303                for (int i = 0; i < packageIdentifiers.size(); i++) {
304                    final String name = (String) packageIdentifiers.valueAt(i);
305
306                    if (packageName.equals(name)) {
307                        return packageIdentifiers.keyAt(i);
308                    }
309                }
310            } catch (Exception e) {
311                throw new RuntimeException("Invalid reflection", e);
312            }
313            throw new RuntimeException("Package not found: " + packageName);
314        }
315
316        @Override
317        public Application getApplication() {
318            try {
319                return (Application) mCurrentApplicationMethod.invoke(null);
320            } catch (Exception e) {
321                throw new RuntimeException("Invalid reflection", e);
322            }
323        }
324
325        @Override
326        public String getErrorString(Context context, int errorCode) {
327            try {
328                return (String) mGetStringMethod.invoke(null, errorCode, context);
329            } catch (Exception e) {
330                throw new RuntimeException("Invalid reflection", e);
331            }
332        }
333
334        @Override
335        public void addWebViewAssetPath(Context context) {
336            try {
337                PackageInfo info = (PackageInfo) mGetLoadedPackageInfoMethod.invoke(null);
338                mAddAssetPathMethod.invoke(context.getAssets(), info.applicationInfo.sourceDir);
339            } catch (Exception e) {
340                throw new RuntimeException("Invalid reflection", e);
341            }
342        }
343    }
344}
345
346