blob: 7be0eadf895eb2359a54b2ef9c5739c352541af0 [file] [log] [blame]
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +01001/*
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.server.webkit;
18
Gustav Sennton6258dcd2015-10-30 19:25:37 +000019import android.app.ActivityManagerNative;
20import android.app.AppGlobals;
Ben Murdochdc00a842014-07-17 14:55:00 +010021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
Gustav Sennton6258dcd2015-10-30 19:25:37 +000025import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.Signature;
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010029import android.os.Binder;
30import android.os.Process;
Gustav Sennton6258dcd2015-10-30 19:25:37 +000031import android.os.RemoteException;
32import android.provider.Settings;
33import android.provider.Settings.Secure;
34import android.util.AndroidRuntimeException;
Primiano Tucci810c0522014-07-25 18:03:16 +010035import android.util.Slog;
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010036import android.webkit.IWebViewUpdateService;
Gustav Sennton6258dcd2015-10-30 19:25:37 +000037import android.webkit.WebViewProviderInfo;
38import android.webkit.WebViewProviderResponse;
Ben Murdochdc00a842014-07-17 14:55:00 +010039import android.webkit.WebViewFactory;
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010040
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +010041import com.android.server.SystemService;
42
Gustav Sennton6258dcd2015-10-30 19:25:37 +000043import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.Iterator;
46import java.util.List;
47
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010048/**
49 * Private service to wait for the updatable WebView to be ready for use.
50 * @hide
51 */
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +010052public class WebViewUpdateService extends SystemService {
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010053
54 private static final String TAG = "WebViewUpdateService";
Gustav Sennton6258dcd2015-10-30 19:25:37 +000055 private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000.
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010056
Gustav Sennton6258dcd2015-10-30 19:25:37 +000057 // Keeps track of the number of running relro creations
58 private int mNumRelroCreationsStarted = 0;
59 private int mNumRelroCreationsFinished = 0;
60 // Implies that we need to rerun relro creation because we are using an out-of-date package
61 private boolean mWebViewPackageDirty = false;
62 // Set to true when the current provider is being replaced
63 private boolean mCurrentProviderBeingReplaced = false;
64 private boolean mAnyWebViewInstalled = false;
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +010065
Gustav Sennton6258dcd2015-10-30 19:25:37 +000066 private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
67
68 // The WebView package currently in use (or the one we are preparing).
69 private PackageInfo mCurrentWebViewPackage = null;
70 // The WebView providers that are currently available.
71 private WebViewProviderInfo[] mCurrentValidWebViewPackages = null;
Gustav Sennton6ce92c92015-10-23 11:10:39 +010072
Ben Murdochdc00a842014-07-17 14:55:00 +010073 private BroadcastReceiver mWebViewUpdatedReceiver;
74
75 public WebViewUpdateService(Context context) {
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +010076 super(context);
77 }
78
79 @Override
80 public void onStart() {
Ben Murdochdc00a842014-07-17 14:55:00 +010081 mWebViewUpdatedReceiver = new BroadcastReceiver() {
82 @Override
83 public void onReceive(Context context, Intent intent) {
Gustav Sennton6258dcd2015-10-30 19:25:37 +000084 // When a package is replaced we will receive two intents, one representing
85 // the removal of the old package and one representing the addition of the
86 // new package.
87 // In the case where we receive an intent to remove the old version of the
88 // package that is being replaced we set a flag here and early-out so that we
89 // don't change provider while replacing the current package (we will instead
90 // change provider when the new version of the package is being installed).
Gustav Sennton3098cf22015-11-10 03:33:09 +000091 if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
Gustav Sennton6258dcd2015-10-30 19:25:37 +000092 && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
93 synchronized(this) {
94 if (mCurrentWebViewPackage == null) return;
95
96 String webViewPackage = "package:" + mCurrentWebViewPackage.packageName;
97 if (webViewPackage.equals(intent.getDataString()))
98 mCurrentProviderBeingReplaced = true;
99 }
100
Gustav Sennton3098cf22015-11-10 03:33:09 +0000101 return;
102 }
103
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000104 for (WebViewProviderInfo provider : WebViewFactory.getWebViewPackages()) {
105 String webviewPackage = "package:" + provider.packageName;
Gustav Sennton6ce92c92015-10-23 11:10:39 +0100106
107 if (webviewPackage.equals(intent.getDataString())) {
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000108 boolean updateWebView = false;
109 boolean removedOldPackage = false;
110 String oldProviderName = null;
111 PackageInfo newPackage = null;
112 synchronized(WebViewUpdateService.this) {
113 try {
114 updateValidWebViewPackages();
115 newPackage = findPreferredWebViewPackage();
116 if (mCurrentWebViewPackage != null)
117 oldProviderName = mCurrentWebViewPackage.packageName;
118 // Only trigger update actions if the updated package is the one
119 // that will be used, or the one that was in use before the
120 // update, or if we haven't seen a valid WebView package before.
121 updateWebView =
122 provider.packageName.equals(newPackage.packageName)
123 || provider.packageName.equals(oldProviderName)
124 || mCurrentWebViewPackage == null;
125 // We removed the old package if we received an intent to remove
126 // or replace the old package.
127 removedOldPackage =
128 provider.packageName.equals(oldProviderName);
129 if (updateWebView) {
130 onWebViewProviderChanged(newPackage);
131 }
132 } catch (WebViewFactory.MissingWebViewPackageException e) {
133 Slog.e(TAG, "Could not find valid WebView package to create " +
134 "relro with " + e);
135 }
136 }
137 if(updateWebView && !removedOldPackage && oldProviderName != null) {
138 // If the provider change is the result of adding or replacing a
139 // package that was not the previous provider then we must kill
140 // packages dependent on the old package ourselves. The framework
141 // only kills dependents of packages that are being removed.
142 try {
143 ActivityManagerNative.getDefault().killPackageDependents(
144 oldProviderName, getContext().getUserId());
145 } catch (RemoteException e) {
146 }
Gustav Sennton6ce92c92015-10-23 11:10:39 +0100147 }
148 return;
149 }
Ben Murdochdc00a842014-07-17 14:55:00 +0100150 }
151 }
152 };
153 IntentFilter filter = new IntentFilter();
Gustav Sennton3098cf22015-11-10 03:33:09 +0000154 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
155 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
Ben Murdochdc00a842014-07-17 14:55:00 +0100156 filter.addDataScheme("package");
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100157 getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +0100158
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100159 publishBinderService("webviewupdate", new BinderService());
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +0100160 }
Ben Murdochdc00a842014-07-17 14:55:00 +0100161
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000162 /**
163 * Perform any WebView loading preparations that must happen at boot from the system server,
164 * after the package manager has started or after an update to the webview is installed.
165 * This must be called in the system server.
166 * Currently, this means spawning the child processes which will create the relro files.
167 */
168 public void prepareWebViewInSystemServer() {
169 try {
170 synchronized(this) {
171 updateValidWebViewPackages();
172 mCurrentWebViewPackage = findPreferredWebViewPackage();
173 onWebViewProviderChanged(mCurrentWebViewPackage);
174 }
175 } catch (Throwable t) {
176 // Log and discard errors at this stage as we must not crash the system server.
177 Slog.e(TAG, "error preparing webview provider from system server", t);
Ben Murdochdc00a842014-07-17 14:55:00 +0100178 }
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000179 }
180
181
182 /**
183 * Change WebView provider and provider setting and kill packages using the old provider.
184 */
185 private void changeProviderAndSetting(String newProviderName) {
186 PackageInfo oldPackage = null;
187 PackageInfo newPackage = null;
188 synchronized(this) {
189 oldPackage = mCurrentWebViewPackage;
190 updateUserSetting(newProviderName);
191
192 try {
193 newPackage = findPreferredWebViewPackage();
194 if (oldPackage != null && newPackage.packageName.equals(oldPackage.packageName)) {
195 // If we don't perform the user change, revert the settings change.
196 updateUserSetting(newPackage.packageName);
197 return;
198 }
199 } catch (WebViewFactory.MissingWebViewPackageException e) {
200 Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView package "
201 + e);
202 // If we don't perform the user change but don't have an installed WebView package,
203 // we will have changed the setting and it will be used when a package is available.
204 return;
205 }
206 onWebViewProviderChanged(newPackage);
207 }
208 // Kill apps using the old provider
209 try {
210 if (oldPackage != null) {
211 ActivityManagerNative.getDefault().killPackageDependents(
212 oldPackage.packageName, getContext().getUserId());
213 }
214 } catch (RemoteException e) {
215 }
216 return;
217 }
218
219 /**
220 * This is called when we change WebView provider, either when the current provider is updated
221 * or a new provider is chosen / takes precedence.
222 */
223 private void onWebViewProviderChanged(PackageInfo newPackage) {
224 synchronized(this) {
225 mAnyWebViewInstalled = true;
226 // If we have changed provider then the replacement of the old provider is
227 // irrelevant - we can only have chosen a new provider if its package is available.
228 mCurrentProviderBeingReplaced = false;
229 if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
230 mCurrentWebViewPackage = newPackage;
231 updateUserSetting(newPackage.packageName);
232
233 // The relro creations might 'finish' (not start at all) before
234 // WebViewFactory.onWebViewProviderChanged which means we might not know the number
235 // of started creations before they finish.
236 mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
237 mNumRelroCreationsFinished = 0;
238 mNumRelroCreationsStarted = WebViewFactory.onWebViewProviderChanged(newPackage);
239 // If the relro creations finish before we know the number of started creations we
240 // will have to do any cleanup/notifying here.
241 checkIfRelrosDoneLocked();
242 } else {
243 mWebViewPackageDirty = true;
244 }
245 }
246 }
247
248 /**
249 * Updates the currently valid WebView provider packages.
250 * Should be used when a provider has been installed or removed.
251 * @hide
252 * */
253 private void updateValidWebViewPackages() {
254 List<WebViewProviderInfo> webViewProviders =
255 new ArrayList<WebViewProviderInfo>(Arrays.asList(WebViewFactory.getWebViewPackages()));
256 Iterator<WebViewProviderInfo> it = webViewProviders.iterator();
257 // remove non-valid packages
258 while(it.hasNext()) {
259 WebViewProviderInfo current = it.next();
260 if (!current.isValidProvider())
261 it.remove();
262 }
263 synchronized(this) {
264 mCurrentValidWebViewPackages =
265 webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
266 }
267 }
268
269 private static String getUserChosenWebViewProvider() {
270 return Settings.Secure.getString(AppGlobals.getInitialApplication().getContentResolver(),
271 Settings.Secure.WEBVIEW_PROVIDER);
272 }
273
274 private void updateUserSetting(String newProviderName) {
275 Settings.Secure.putString(getContext().getContentResolver(),
276 Settings.Secure.WEBVIEW_PROVIDER,
277 newProviderName == null ? "" : newProviderName);
278 }
279
280 /**
281 * Returns either the package info of the WebView provider determined in the following way:
282 * If the user has chosen a provider then use that if it is valid,
283 * otherwise use the first package in the webview priority list that is valid.
284 *
285 * @hide
286 */
287 private PackageInfo findPreferredWebViewPackage() {
288 WebViewProviderInfo[] providers = mCurrentValidWebViewPackages;
289
290 String userChosenProvider = getUserChosenWebViewProvider();
291
292 // If the user has chosen provider, use that
293 for (WebViewProviderInfo provider : providers) {
294 if (provider.packageName.equals(userChosenProvider)) {
295 return provider.getPackageInfo();
296 }
297 }
298
299 // User did not choose, or the choice failed, use the most stable provider available
300 for (WebViewProviderInfo provider : providers) {
301 return provider.getPackageInfo();
302 }
303 mAnyWebViewInstalled = false;
304 throw new WebViewFactory.MissingWebViewPackageException(
305 "Could not find a loadable WebView package");
306 }
307
308 /**
309 * Returns whether WebView is ready and is not going to go through its preparation phase again
310 * directly.
311 */
312 private boolean webViewIsReadyLocked() {
313 return !mWebViewPackageDirty
314 && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
315 && !mCurrentProviderBeingReplaced
316 // The current package might be replaced though we haven't received an intent declaring
317 // this yet, the following flag makes anyone loading WebView to wait in this case.
318 && mAnyWebViewInstalled;
319 }
320
321 private void checkIfRelrosDoneLocked() {
322 if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
323 if (mWebViewPackageDirty) {
324 mWebViewPackageDirty = false;
325 // If we have changed provider since we started the relro creation we need to
326 // redo the whole process using the new package instead.
327 // Though, if the current provider package is being replaced we don't want to change
328 // provider here since we will perform the change either when the package is added
329 // again or when we switch to another provider (whichever comes first).
330 if (!mCurrentProviderBeingReplaced) {
331 PackageInfo newPackage = findPreferredWebViewPackage();
332 onWebViewProviderChanged(newPackage);
333 }
334 } else {
335 this.notifyAll();
336 }
337 }
Ben Murdochdc00a842014-07-17 14:55:00 +0100338 }
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100339
340 private class BinderService extends IWebViewUpdateService.Stub {
341
342 /**
343 * The shared relro process calls this to notify us that it's done trying to create a relro
344 * file. This method gets called even if the relro creation has failed or the process
345 * crashed.
346 */
347 @Override // Binder call
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000348 public void notifyRelroCreationCompleted() {
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100349 // Verify that the caller is either the shared relro process (nominal case) or the
350 // system server (only in the case the relro process crashes and we get here via the
351 // crashHandler).
352 if (Binder.getCallingUid() != Process.SHARED_RELRO_UID &&
353 Binder.getCallingUid() != Process.SYSTEM_UID) {
354 return;
355 }
356
357 synchronized (WebViewUpdateService.this) {
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000358 mNumRelroCreationsFinished++;
359 checkIfRelrosDoneLocked();
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100360 }
361 }
362
363 /**
364 * WebViewFactory calls this to block WebView loading until the relro file is created.
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000365 * Returns the WebView provider for which we create relro files.
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100366 */
367 @Override // Binder call
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000368 public WebViewProviderResponse waitForAndGetProvider() {
Primiano Tuccie76e81a2014-07-29 16:38:33 +0100369 // The WebViewUpdateService depends on the prepareWebViewInSystemServer call, which
370 // happens later (during the PHASE_ACTIVITY_MANAGER_READY) in SystemServer.java. If
371 // another service there tries to bring up a WebView in the between, the wait below
372 // would deadlock without the check below.
373 if (Binder.getCallingPid() == Process.myPid()) {
374 throw new IllegalStateException("Cannot create a WebView from the SystemServer");
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100375 }
376
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000377 PackageInfo webViewPackage = null;
Primiano Tuccie76e81a2014-07-29 16:38:33 +0100378 final long NS_PER_MS = 1000000;
379 final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000380 boolean webViewReady = false;
381 int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100382 synchronized (WebViewUpdateService.this) {
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000383 webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
384 while (!webViewReady) {
Primiano Tuccie76e81a2014-07-29 16:38:33 +0100385 final long timeNowMs = System.nanoTime() / NS_PER_MS;
386 if (timeNowMs >= timeoutTimeMs) break;
387 try {
388 WebViewUpdateService.this.wait(timeoutTimeMs - timeNowMs);
389 } catch (InterruptedException e) {}
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000390 webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
391 }
392 // Make sure we return the provider that was used to create the relro file
393 webViewPackage = WebViewUpdateService.this.mCurrentWebViewPackage;
394 if (webViewReady) {
395 } else if (mCurrentProviderBeingReplaced) {
396 // It is important that we check this flag before the one representing WebView
397 // being installed, otherwise we might think there is no WebView though the
398 // current one is just being replaced.
399 webViewStatus = WebViewFactory.LIBLOAD_WEBVIEW_BEING_REPLACED;
400 } else if (!mAnyWebViewInstalled) {
401 webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
402 } else {
403 // Either the current relro creation isn't done yet, or the new relro creatioin
404 // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
405 webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100406 }
407 }
Gustav Sennton6258dcd2015-10-30 19:25:37 +0000408 if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
409 return new WebViewProviderResponse(webViewPackage, webViewStatus);
410 }
411
412 /**
413 * This is called from DeveloperSettings when the user changes WebView provider.
414 */
415 @Override // Binder call
416 public void changeProviderAndSetting(String newProvider) {
417 if (getContext().checkCallingPermission(
418 android.Manifest.permission.WRITE_SECURE_SETTINGS)
419 != PackageManager.PERMISSION_GRANTED) {
420 String msg = "Permission Denial: changeProviderAndSetting() from pid="
421 + Binder.getCallingPid()
422 + ", uid=" + Binder.getCallingUid()
423 + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
424 Slog.w(TAG, msg);
425 throw new SecurityException(msg);
426 }
427
428 WebViewUpdateService.this.changeProviderAndSetting(newProvider);
429 }
430
431 @Override // Binder call
432 public WebViewProviderInfo[] getValidWebViewPackages() {
433 synchronized(WebViewUpdateService.this) {
434 return mCurrentValidWebViewPackages;
435 }
436 }
437
438 @Override // Binder call
439 public String getCurrentWebViewPackageName() {
440 synchronized(WebViewUpdateService.this) {
441 if (WebViewUpdateService.this.mCurrentWebViewPackage == null)
442 return null;
443 return WebViewUpdateService.this.mCurrentWebViewPackage.packageName;
444 }
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100445 }
Torne (Richard Coles)4dbeb352014-07-29 19:14:24 +0100446 }
Torne (Richard Coles)08cfaf62014-05-08 16:07:05 +0100447}