1/*
2 * Copyright (C) 2016 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 android.webkit;
18
19import android.app.LoadedApk;
20import android.content.pm.ApplicationInfo;
21import android.content.pm.PackageInfo;
22import android.os.AsyncTask;
23import android.os.Build;
24import android.os.ChildZygoteProcess;
25import android.os.Process;
26import android.os.ZygoteProcess;
27import android.text.TextUtils;
28import android.util.Log;
29
30import com.android.internal.annotations.GuardedBy;
31
32import java.io.File;
33import java.util.ArrayList;
34import java.util.List;
35
36/** @hide */
37public class WebViewZygote {
38    private static final String LOGTAG = "WebViewZygote";
39
40    /**
41     * Lock object that protects all other static members.
42     */
43    private static final Object sLock = new Object();
44
45    /**
46     * Instance that maintains the socket connection to the zygote. This is {@code null} if the
47     * zygote is not running or is not connected.
48     */
49    @GuardedBy("sLock")
50    private static ChildZygoteProcess sZygote;
51
52    /**
53     * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
54     */
55    @GuardedBy("sLock")
56    private static PackageInfo sPackage;
57
58    /**
59     * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
60     * #onWebViewProviderChanged().
61     */
62    @GuardedBy("sLock")
63    private static ApplicationInfo sPackageOriginalAppInfo;
64
65    /**
66     * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
67     * will not be started.
68     */
69    @GuardedBy("sLock")
70    private static boolean sMultiprocessEnabled = false;
71
72    public static ZygoteProcess getProcess() {
73        synchronized (sLock) {
74            if (sZygote != null) return sZygote;
75
76            connectToZygoteIfNeededLocked();
77            return sZygote;
78        }
79    }
80
81    public static String getPackageName() {
82        synchronized (sLock) {
83            return sPackage.packageName;
84        }
85    }
86
87    public static boolean isMultiprocessEnabled() {
88        synchronized (sLock) {
89            return sMultiprocessEnabled && sPackage != null;
90        }
91    }
92
93    public static void setMultiprocessEnabled(boolean enabled) {
94        synchronized (sLock) {
95            sMultiprocessEnabled = enabled;
96
97            // When toggling between multi-process being on/off, start or stop the
98            // zygote. If it is enabled and the zygote is not yet started, launch it.
99            // Otherwise, kill it. The name may be null if the package information has
100            // not yet been resolved.
101            if (enabled) {
102                // Run on a background thread as this waits for the zygote to start and we don't
103                // want to block the caller on this. It's okay if this is delayed as anyone trying
104                // to use the zygote will call it first anyway.
105                AsyncTask.THREAD_POOL_EXECUTOR.execute(WebViewZygote::getProcess);
106            } else {
107                // No need to run this in the background, it's very brief.
108                stopZygoteLocked();
109            }
110        }
111    }
112
113    public static void onWebViewProviderChanged(PackageInfo packageInfo,
114                                                ApplicationInfo originalAppInfo) {
115        synchronized (sLock) {
116            sPackage = packageInfo;
117            sPackageOriginalAppInfo = originalAppInfo;
118
119            // If multi-process is not enabled, then do not start the zygote service.
120            if (!sMultiprocessEnabled) {
121                return;
122            }
123
124            stopZygoteLocked();
125        }
126    }
127
128    @GuardedBy("sLock")
129    private static void stopZygoteLocked() {
130        if (sZygote != null) {
131            // Close the connection and kill the zygote process. This will not cause
132            // child processes to be killed by itself. But if this is called in response to
133            // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater
134            // will kill all processes that depend on the WebView package.
135            sZygote.close();
136            Process.killProcess(sZygote.getPid());
137            sZygote = null;
138        }
139    }
140
141    @GuardedBy("sLock")
142    private static void connectToZygoteIfNeededLocked() {
143        if (sZygote != null) {
144            return;
145        }
146
147        if (sPackage == null) {
148            Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
149            return;
150        }
151
152        try {
153            sZygote = Process.zygoteProcess.startChildZygote(
154                    "com.android.internal.os.WebViewZygoteInit",
155                    "webview_zygote",
156                    Process.WEBVIEW_ZYGOTE_UID,
157                    Process.WEBVIEW_ZYGOTE_UID,
158                    null,  // gids
159                    0,  // runtimeFlags
160                    "webview_zygote",  // seInfo
161                    sPackage.applicationInfo.primaryCpuAbi,  // abi
162                    null);  // instructionSet
163
164            // All the work below is usually done by LoadedApk, but the zygote can't talk to
165            // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
166            // doesn't have an ActivityThread and can't use Binder.
167            // Instead, figure out the paths here, in the system server where we have access to
168            // the package manager. Reuse the logic from LoadedApk to determine the correct
169            // paths and pass them to the zygote as strings.
170            final List<String> zipPaths = new ArrayList<>(10);
171            final List<String> libPaths = new ArrayList<>(10);
172            LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
173            final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
174            final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
175                    TextUtils.join(File.pathSeparator, zipPaths);
176
177            String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
178
179            // In the case where the ApplicationInfo has been modified by the stub WebView,
180            // we need to use the original ApplicationInfo to determine what the original classpath
181            // would have been to use as a cache key.
182            LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
183            final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
184                    TextUtils.join(File.pathSeparator, zipPaths);
185
186            ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
187
188            Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
189            sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
190                                         Build.SUPPORTED_ABIS[0]);
191        } catch (Exception e) {
192            Log.e(LOGTAG, "Error connecting to webview zygote", e);
193            stopZygoteLocked();
194        }
195    }
196}
197