/*
|
* Copyright (C) 2016 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
package com.android.server.webkit;
|
|
import android.content.Context;
|
import android.content.pm.PackageInfo;
|
import android.os.AsyncTask;
|
import android.os.UserHandle;
|
import android.util.Slog;
|
import android.webkit.WebViewProviderInfo;
|
import android.webkit.WebViewProviderResponse;
|
|
import java.io.PrintWriter;
|
|
/**
|
* Implementation of the WebViewUpdateService.
|
* This class doesn't depend on the android system like the actual Service does and can be used
|
* directly by tests (as long as they implement a SystemInterface).
|
*
|
* This class keeps track of and prepares the current WebView implementation, and needs to keep
|
* track of a couple of different things such as what package is used as WebView implementation.
|
*
|
* The public methods in this class are accessed from WebViewUpdateService either on the UI thread
|
* or on one of multiple Binder threads. The WebView preparation code shares state between threads
|
* meaning that code that chooses a new WebView implementation or checks which implementation is
|
* being used needs to hold a lock.
|
*
|
* The WebViewUpdateService can be accessed in a couple of different ways.
|
* 1. It is started from the SystemServer at boot - at that point we just initiate some state such
|
* as the WebView preparation class.
|
* 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
|
* and the WebViewUpdateService should not have been accessed before this call. In this call we
|
* migrate away from the old fallback logic if necessary and then choose WebView implementation for
|
* the first time.
|
* 3. The update service listens for Intents related to package installs and removals. These intents
|
* are received and processed on the UI thread. Each intent can result in changing WebView
|
* implementation.
|
* 4. The update service can be reached through Binder calls which are handled on specific binder
|
* threads. These calls can be made from any process. Generally they are used for changing WebView
|
* implementation (from Settings), getting information about the current WebView implementation (for
|
* loading WebView into an app process), or notifying the service about Relro creation being
|
* completed.
|
*
|
* @hide
|
*/
|
public class WebViewUpdateServiceImpl {
|
private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
|
|
private SystemInterface mSystemInterface;
|
private WebViewUpdater mWebViewUpdater;
|
final private Context mContext;
|
|
private final static int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
|
private final static int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
|
|
public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
|
mContext = context;
|
mSystemInterface = systemInterface;
|
mWebViewUpdater = new WebViewUpdater(mContext, mSystemInterface);
|
}
|
|
void packageStateChanged(String packageName, int changedState, int userId) {
|
// We don't early out here in different cases where we could potentially early-out (e.g. if
|
// we receive PACKAGE_CHANGED for another user than the system user) since that would
|
// complicate this logic further and open up for more edge cases.
|
mWebViewUpdater.packageStateChanged(packageName, changedState);
|
}
|
|
void prepareWebViewInSystemServer() {
|
migrateFallbackStateOnBoot();
|
mWebViewUpdater.prepareWebViewInSystemServer();
|
if (getCurrentWebViewPackage() == null) {
|
// We didn't find a valid WebView implementation. Try explicitly re-enabling the
|
// fallback package for all users in case it was disabled, even if we already did the
|
// one-time migration before. If this actually changes the state, WebViewUpdater will
|
// see the PackageManager broadcast shortly and try again.
|
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
|
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
|
if (fallbackProvider != null) {
|
Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
|
mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
|
true);
|
} else {
|
Slog.e(TAG, "No valid provider and no fallback available.");
|
}
|
}
|
|
boolean multiProcessEnabled = isMultiProcessEnabled();
|
mSystemInterface.notifyZygote(multiProcessEnabled);
|
if (multiProcessEnabled) {
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
|
}
|
}
|
|
void startZygoteWhenReady() {
|
// Wait on a background thread for RELRO creation to be done. We ignore the return value
|
// because even if RELRO creation failed we still want to start the zygote.
|
waitForAndGetProvider();
|
mSystemInterface.ensureZygoteStarted();
|
}
|
|
void handleNewUser(int userId) {
|
// The system user is always started at boot, and by that point we have already run one
|
// round of the package-changing logic (through prepareWebViewInSystemServer()), so early
|
// out here.
|
if (userId == UserHandle.USER_SYSTEM) return;
|
handleUserChange();
|
}
|
|
void handleUserRemoved(int userId) {
|
handleUserChange();
|
}
|
|
/**
|
* Called when a user was added or removed to ensure WebView preparation is triggered.
|
* This has to be done since the WebView package we use depends on the enabled-state
|
* of packages for all users (so adding or removing a user might cause us to change package).
|
*/
|
private void handleUserChange() {
|
// Potentially trigger package-changing logic.
|
mWebViewUpdater.updateCurrentWebViewPackage(null);
|
}
|
|
void notifyRelroCreationCompleted() {
|
mWebViewUpdater.notifyRelroCreationCompleted();
|
}
|
|
WebViewProviderResponse waitForAndGetProvider() {
|
return mWebViewUpdater.waitForAndGetProvider();
|
}
|
|
String changeProviderAndSetting(String newProvider) {
|
return mWebViewUpdater.changeProviderAndSetting(newProvider);
|
}
|
|
WebViewProviderInfo[] getValidWebViewPackages() {
|
return mWebViewUpdater.getValidWebViewPackages();
|
}
|
|
WebViewProviderInfo[] getWebViewPackages() {
|
return mSystemInterface.getWebViewPackages();
|
}
|
|
PackageInfo getCurrentWebViewPackage() {
|
return mWebViewUpdater.getCurrentWebViewPackage();
|
}
|
|
/**
|
* If the fallback logic is enabled, re-enable any fallback package for all users, then
|
* disable the fallback logic.
|
*
|
* This migrates away from the old fallback mechanism to the new state where packages are never
|
* automatically enableenableisabled.
|
*/
|
private void migrateFallbackStateOnBoot() {
|
if (!mSystemInterface.isFallbackLogicEnabled()) return;
|
|
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
|
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
|
if (fallbackProvider != null) {
|
Slog.i(TAG, "One-time migration: enabling " + fallbackProvider.packageName);
|
mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, true);
|
} else {
|
Slog.i(TAG, "Skipping one-time migration: no fallback provider");
|
}
|
mSystemInterface.enableFallbackLogic(false);
|
}
|
|
/**
|
* Returns the only fallback provider in the set of given packages, or null if there is none.
|
*/
|
private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
|
for (WebViewProviderInfo provider : webviewPackages) {
|
if (provider.isFallback) {
|
return provider;
|
}
|
}
|
return null;
|
}
|
|
boolean isMultiProcessEnabled() {
|
int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
|
if (mSystemInterface.isMultiProcessDefaultEnabled()) {
|
// Multiprocess should be enabled unless the user has turned it off manually.
|
return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
|
} else {
|
// Multiprocess should not be enabled, unless the user has turned it on manually.
|
return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
|
}
|
}
|
|
void enableMultiProcess(boolean enable) {
|
PackageInfo current = getCurrentWebViewPackage();
|
mSystemInterface.setMultiProcessSetting(mContext,
|
enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
|
mSystemInterface.notifyZygote(enable);
|
if (current != null) {
|
mSystemInterface.killPackageDependents(current.packageName);
|
}
|
}
|
|
/**
|
* Dump the state of this Service.
|
*/
|
void dumpState(PrintWriter pw) {
|
pw.println("Current WebView Update Service state");
|
pw.println(String.format(" Fallback logic enabled: %b",
|
mSystemInterface.isFallbackLogicEnabled()));
|
pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
|
mWebViewUpdater.dumpState(pw);
|
}
|
}
|