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