/* * 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); } }