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 static android.webkit.Flags.updateServiceV2;
20 
21 import android.content.pm.PackageInfo;
22 import android.os.Build;
23 import android.os.ChildZygoteProcess;
24 import android.os.Process;
25 import android.os.ZygoteProcess;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.os.Zygote;
31 
32 /** @hide */
33 public class WebViewZygote {
34     private static final String LOGTAG = "WebViewZygote";
35 
36     /**
37      * Lock object that protects all other static members.
38      */
39     private static final Object sLock = new Object();
40 
41     /**
42      * Instance that maintains the socket connection to the zygote. This is {@code null} if the
43      * zygote is not running or is not connected.
44      */
45     @GuardedBy("sLock")
46     private static ChildZygoteProcess sZygote;
47 
48     /**
49      * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
50      */
51     @GuardedBy("sLock")
52     private static PackageInfo sPackage;
53 
54     /**
55      * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote will
56      * not be started. Should be removed entirely after we remove the updateServiceV2 flag.
57      */
58     @GuardedBy("sLock")
59     private static boolean sMultiprocessEnabled = false;
60 
getProcess()61     public static ZygoteProcess getProcess() {
62         synchronized (sLock) {
63             if (sZygote != null) return sZygote;
64 
65             connectToZygoteIfNeededLocked();
66             return sZygote;
67         }
68     }
69 
getPackageName()70     public static String getPackageName() {
71         synchronized (sLock) {
72             return sPackage.packageName;
73         }
74     }
75 
isMultiprocessEnabled()76     public static boolean isMultiprocessEnabled() {
77         synchronized (sLock) {
78             if (updateServiceV2()) {
79                 return sPackage != null;
80             } else {
81                 return sMultiprocessEnabled && sPackage != null;
82             }
83         }
84     }
85 
setMultiprocessEnabled(boolean enabled)86     public static void setMultiprocessEnabled(boolean enabled) {
87         if (updateServiceV2()) {
88             throw new IllegalStateException(
89                     "setMultiprocessEnabled shouldn't be called if update_service_v2 flag is set.");
90         }
91         synchronized (sLock) {
92             sMultiprocessEnabled = enabled;
93 
94             // When multi-process is disabled, kill the zygote. When it is enabled,
95             // the zygote will be started when it is first needed in getProcess().
96             if (!enabled) {
97                 stopZygoteLocked();
98             }
99         }
100     }
101 
onWebViewProviderChanged(PackageInfo packageInfo)102     static void onWebViewProviderChanged(PackageInfo packageInfo) {
103         synchronized (sLock) {
104             sPackage = packageInfo;
105 
106             // If multi-process is not enabled, then do not start the zygote service.
107             // Only check sMultiprocessEnabled if updateServiceV2 is not enabled.
108             if (!updateServiceV2() && !sMultiprocessEnabled) {
109                 return;
110             }
111 
112             stopZygoteLocked();
113         }
114     }
115 
116     @GuardedBy("sLock")
stopZygoteLocked()117     private static void stopZygoteLocked() {
118         if (sZygote != null) {
119             // Close the connection and kill the zygote process. This will not cause
120             // child processes to be killed by itself. But if this is called in response to
121             // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater
122             // will kill all processes that depend on the WebView package.
123             sZygote.close();
124             Process.killProcess(sZygote.getPid());
125             sZygote = null;
126         }
127     }
128 
129     @GuardedBy("sLock")
connectToZygoteIfNeededLocked()130     private static void connectToZygoteIfNeededLocked() {
131         if (sZygote != null) {
132             return;
133         }
134 
135         if (sPackage == null) {
136             Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
137             return;
138         }
139 
140         try {
141             String abi = sPackage.applicationInfo.primaryCpuAbi;
142             int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
143                     sPackage.applicationInfo, null);
144             sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
145                     "com.android.internal.os.WebViewZygoteInit",
146                     "webview_zygote",
147                     Process.WEBVIEW_ZYGOTE_UID,
148                     Process.WEBVIEW_ZYGOTE_UID,
149                     null,  // gids
150                     runtimeFlags,
151                     "webview_zygote",  // seInfo
152                     abi,  // abi
153                     TextUtils.join(",", Build.SUPPORTED_ABIS),
154                     null, // instructionSet
155                     Process.FIRST_ISOLATED_UID,
156                     Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly
157             ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
158             sZygote.preloadApp(sPackage.applicationInfo, abi);
159         } catch (Exception e) {
160             Log.e(LOGTAG, "Error connecting to webview zygote", e);
161             stopZygoteLocked();
162         }
163     }
164 }
165