1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.webview.chromium;
18
19import android.app.ActivityManager;
20import android.app.ActivityThread;
21import android.content.ComponentCallbacks2;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.os.Build;
25import android.os.Looper;
26import android.util.Log;
27import android.webkit.CookieManager;
28import android.webkit.GeolocationPermissions;
29import android.webkit.WebIconDatabase;
30import android.webkit.WebStorage;
31import android.webkit.WebView;
32import android.webkit.WebViewDatabase;
33import android.webkit.WebViewFactoryProvider;
34import android.webkit.WebViewProvider;
35
36import org.chromium.android_webview.AwBrowserContext;
37import org.chromium.android_webview.AwBrowserProcess;
38import org.chromium.android_webview.AwContents;
39import org.chromium.android_webview.AwCookieManager;
40import org.chromium.android_webview.AwDevToolsServer;
41import org.chromium.android_webview.AwFormDatabase;
42import org.chromium.android_webview.AwGeolocationPermissions;
43import org.chromium.android_webview.AwQuotaManagerBridge;
44import org.chromium.android_webview.AwSettings;
45import org.chromium.base.CommandLine;
46import org.chromium.base.MemoryPressureListener;
47import org.chromium.base.PathService;
48import org.chromium.base.ThreadUtils;
49import org.chromium.content.app.ContentMain;
50import org.chromium.content.app.LibraryLoader;
51import org.chromium.content.browser.ContentViewStatics;
52import org.chromium.content.browser.ResourceExtractor;
53import org.chromium.content.common.ProcessInitException;
54
55import java.lang.ref.WeakReference;
56import java.util.ArrayList;
57
58public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
59
60    private final String TAG = "WebViewChromiumFactoryProvider";
61
62    private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs";
63    private static final String COMMAND_LINE_FILE = "/data/local/tmp/webview-command-line";
64
65    // Guards accees to the other members, and is notifyAll() signalled on the UI thread
66    // when the chromium process has been started.
67    private final Object mLock = new Object();
68
69    // Initialization guarded by mLock.
70    private AwBrowserContext mBrowserContext;
71    private Statics mStaticMethods;
72    private GeolocationPermissionsAdapter mGeolocationPermissions;
73    private CookieManagerAdapter mCookieManager;
74    private WebIconDatabaseAdapter mWebIconDatabase;
75    private WebStorageAdapter mWebStorage;
76    private WebViewDatabaseAdapter mWebViewDatabase;
77    private AwDevToolsServer mDevToolsServer;
78
79    private ArrayList<WeakReference<WebViewChromium>> mWebViewsToStart =
80              new ArrayList<WeakReference<WebViewChromium>>();
81
82    // Read/write protected by mLock.
83    private boolean mStarted;
84
85    public WebViewChromiumFactoryProvider() {
86        // Load chromium library.
87        AwBrowserProcess.loadLibrary();
88        // Load glue-layer support library.
89        System.loadLibrary("webviewchromium_plat_support");
90        ThreadUtils.setWillOverrideUiThread();
91    }
92
93    private void initPlatSupportLibrary() {
94        DrawGLFunctor.setChromiumAwDrawGLFunction(AwContents.getAwDrawGLFunction());
95        AwContents.setAwDrawSWFunctionTable(GraphicsUtils.getDrawSWFunctionTable());
96        AwContents.setAwDrawGLFunctionTable(GraphicsUtils.getDrawGLFunctionTable());
97    }
98
99    private void ensureChromiumStartedLocked(boolean onMainThread) {
100        assert Thread.holdsLock(mLock);
101
102        if (mStarted) {  // Early-out for the common case.
103            return;
104        }
105
106        Looper looper = !onMainThread ? Looper.myLooper() : Looper.getMainLooper();
107        Log.v(TAG, "Binding Chromium to " +
108                (Looper.getMainLooper().equals(looper) ? "main":"background") +
109                " looper " + looper);
110        ThreadUtils.setUiThread(looper);
111
112        if (ThreadUtils.runningOnUiThread()) {
113            startChromiumLocked();
114            return;
115        }
116
117        // We must post to the UI thread to cover the case that the user has invoked Chromium
118        // startup by using the (thread-safe) CookieManager rather than creating a WebView.
119        ThreadUtils.postOnUiThread(new Runnable() {
120            @Override
121            public void run() {
122                synchronized (mLock) {
123                    startChromiumLocked();
124                }
125            }
126        });
127        while (!mStarted) {
128            try {
129                // Important: wait() releases |mLock| so the UI thread can take it :-)
130                mLock.wait();
131            } catch (InterruptedException e) {
132                // Keep trying... eventually the UI thread will process the task we sent it.
133            }
134        }
135    }
136
137    private void startChromiumLocked() {
138        assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread();
139
140        // The post-condition of this method is everything is ready, so notify now to cover all
141        // return paths. (Other threads will not wake-up until we release |mLock|, whatever).
142        mLock.notifyAll();
143
144        if (mStarted) {
145            return;
146        }
147
148        if (Build.IS_DEBUGGABLE) {
149            CommandLine.initFromFile(COMMAND_LINE_FILE);
150        } else {
151            CommandLine.init(null);
152        }
153
154        CommandLine cl = CommandLine.getInstance();
155        // TODO: currently in a relase build the DCHECKs only log. We either need to insall
156        // a report handler with SetLogReportHandler to make them assert, or else compile
157        // them out of the build altogether (b/8284203). Either way, so long they're
158        // compiled in, we may as unconditionally enable them here.
159        cl.appendSwitch("enable-dcheck");
160
161        // TODO: Remove when GL is supported by default in the upstream code.
162        if (!cl.hasSwitch("disable-webview-gl-mode")) {
163            cl.appendSwitch("testing-webview-gl-mode");
164        }
165
166        // We don't need to extract any paks because for WebView, they are
167        // in the system image.
168        ResourceExtractor.setMandatoryPaksToExtract("");
169
170        try {
171            LibraryLoader.ensureInitialized();
172        } catch(ProcessInitException e) {
173            throw new RuntimeException("Error initializing WebView library", e);
174        }
175
176        PathService.override(PathService.DIR_MODULE, "/system/lib/");
177        // TODO: DIR_RESOURCE_PAKS_ANDROID needs to live somewhere sensible,
178        // inlined here for simplicity setting up the HTMLViewer demo. Unfortunately
179        // it can't go into base.PathService, as the native constant it refers to
180        // lives in the ui/ layer. See ui/base/ui_base_paths.h
181        final int DIR_RESOURCE_PAKS_ANDROID = 3003;
182        PathService.override(DIR_RESOURCE_PAKS_ANDROID,
183                "/system/framework/webview/paks");
184
185        AwBrowserProcess.start(ActivityThread.currentApplication());
186        initPlatSupportLibrary();
187
188        if (Build.IS_DEBUGGABLE) {
189            setWebContentsDebuggingEnabled(true);
190        }
191        mStarted = true;
192
193        for (WeakReference<WebViewChromium> wvc : mWebViewsToStart) {
194            WebViewChromium w = wvc.get();
195            if (w != null) {
196                w.startYourEngine();
197            }
198        }
199        mWebViewsToStart.clear();
200        mWebViewsToStart = null;
201    }
202
203    boolean hasStarted() {
204        return mStarted;
205    }
206
207    void startYourEngines(boolean onMainThread) {
208        synchronized (mLock) {
209            ensureChromiumStartedLocked(onMainThread);
210
211        }
212    }
213
214    AwBrowserContext getBrowserContext() {
215        synchronized (mLock) {
216            return getBrowserContextLocked();
217        }
218    }
219
220    private AwBrowserContext getBrowserContextLocked() {
221        assert Thread.holdsLock(mLock);
222        assert mStarted;
223        if (mBrowserContext == null) {
224            mBrowserContext = new AwBrowserContext(
225                    ActivityThread.currentApplication().getSharedPreferences(
226                            CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE));
227        }
228        return mBrowserContext;
229    }
230
231    private void setWebContentsDebuggingEnabled(boolean enable) {
232        if (Looper.myLooper() != ThreadUtils.getUiThreadLooper()) {
233            throw new RuntimeException(
234                    "Toggling of Web Contents Debugging must be done on the UI thread");
235        }
236        if (mDevToolsServer == null) {
237            if (!enable) return;
238            mDevToolsServer = new AwDevToolsServer();
239        }
240        mDevToolsServer.setRemoteDebuggingEnabled(enable);
241    }
242
243
244    @Override
245    public Statics getStatics() {
246        synchronized (mLock) {
247            if (mStaticMethods == null) {
248                // TODO: Optimization potential: most these methods only need the native library
249                // loaded and initialized, not the entire browser process started.
250                // See also http://b/7009882
251                ensureChromiumStartedLocked(true);
252                mStaticMethods = new WebViewFactoryProvider.Statics() {
253                    @Override
254                    public String findAddress(String addr) {
255                        return ContentViewStatics.findAddress(addr);
256                    }
257
258                    @Override
259                    public void setPlatformNotificationsEnabled(boolean enable) {
260                        // noop
261                    }
262
263                    @Override
264                    public String getDefaultUserAgent(Context context) {
265                        return AwSettings.getDefaultUserAgent();
266                    }
267
268                    @Override
269                    public void setWebContentsDebuggingEnabled(boolean enable) {
270                        // Web Contents debugging is always enabled on debug builds.
271                        if (!Build.IS_DEBUGGABLE) {
272                            WebViewChromiumFactoryProvider.this.
273                                    setWebContentsDebuggingEnabled(enable);
274                        }
275                    }
276
277                    public void freeMemoryForTests() {
278                        if (ActivityManager.isRunningInTestHarness()) {
279                            MemoryPressureListener.maybeNotifyMemoryPresure(
280                                    ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
281                        }
282                    }
283                };
284            }
285        }
286        return mStaticMethods;
287    }
288
289    @Override
290    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
291        WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);
292
293        synchronized (mLock) {
294            if (mWebViewsToStart != null) {
295                mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
296            }
297        }
298        ResourceProvider.registerResources(webView.getContext());
299        return wvc;
300    }
301
302    @Override
303    public GeolocationPermissions getGeolocationPermissions() {
304        synchronized (mLock) {
305            if (mGeolocationPermissions == null) {
306                ensureChromiumStartedLocked(true);
307                mGeolocationPermissions = new GeolocationPermissionsAdapter(
308                        getBrowserContextLocked().getGeolocationPermissions());
309            }
310        }
311        return mGeolocationPermissions;
312    }
313
314    @Override
315    public CookieManager getCookieManager() {
316        synchronized (mLock) {
317            if (mCookieManager == null) {
318                if (!mStarted) {
319                    // We can use CookieManager without starting Chromium; the native code
320                    // will bring up just the parts it needs to make this work on a temporary
321                    // basis until Chromium is started for real. The temporary cookie manager
322                    // needs the application context to have been set.
323                    ContentMain.initApplicationContext(ActivityThread.currentApplication());
324                }
325                mCookieManager = new CookieManagerAdapter(new AwCookieManager());
326            }
327        }
328        return mCookieManager;
329    }
330
331    @Override
332    public WebIconDatabase getWebIconDatabase() {
333        synchronized (mLock) {
334            if (mWebIconDatabase == null) {
335                ensureChromiumStartedLocked(true);
336                mWebIconDatabase = new WebIconDatabaseAdapter();
337            }
338        }
339        return mWebIconDatabase;
340    }
341
342    @Override
343    public WebStorage getWebStorage() {
344        synchronized (mLock) {
345            if (mWebStorage == null) {
346                ensureChromiumStartedLocked(true);
347                mWebStorage = new WebStorageAdapter(AwQuotaManagerBridge.getInstance());
348            }
349        }
350        return mWebStorage;
351    }
352
353    @Override
354    public WebViewDatabase getWebViewDatabase(Context context) {
355        synchronized (mLock) {
356            if (mWebViewDatabase == null) {
357                ensureChromiumStartedLocked(true);
358                AwBrowserContext browserContext = getBrowserContextLocked();
359                mWebViewDatabase = new WebViewDatabaseAdapter(
360                        browserContext.getFormDatabase(),
361                        browserContext.getHttpAuthDatabase(context));
362            }
363        }
364        return mWebViewDatabase;
365    }
366}
367