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 
17 package android.webkit;
18 
19 import android.app.LoadedApk;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageInfo;
22 import android.os.AsyncTask;
23 import android.os.Build;
24 import android.os.ChildZygoteProcess;
25 import android.os.Process;
26 import android.os.ZygoteProcess;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.GuardedBy;
31 
32 import java.io.File;
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /** @hide */
37 public 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 
getProcess()72     public static ZygoteProcess getProcess() {
73         synchronized (sLock) {
74             if (sZygote != null) return sZygote;
75 
76             connectToZygoteIfNeededLocked();
77             return sZygote;
78         }
79     }
80 
getPackageName()81     public static String getPackageName() {
82         synchronized (sLock) {
83             return sPackage.packageName;
84         }
85     }
86 
isMultiprocessEnabled()87     public static boolean isMultiprocessEnabled() {
88         synchronized (sLock) {
89             return sMultiprocessEnabled && sPackage != null;
90         }
91     }
92 
setMultiprocessEnabled(boolean enabled)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 
onWebViewProviderChanged(PackageInfo packageInfo, ApplicationInfo originalAppInfo)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")
stopZygoteLocked()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")
connectToZygoteIfNeededLocked()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