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 package com.android.server.webkit;
17 
18 import android.annotation.Nullable;
19 import android.content.Context;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManager.NameNotFoundException;
22 import android.content.pm.Signature;
23 import android.os.AsyncTask;
24 import android.os.UserHandle;
25 import android.util.Slog;
26 import android.webkit.UserPackage;
27 import android.webkit.WebViewFactory;
28 import android.webkit.WebViewProviderInfo;
29 import android.webkit.WebViewProviderResponse;
30 
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Implementation of the WebViewUpdateService.
37  * This class doesn't depend on the android system like the actual Service does and can be used
38  * directly by tests (as long as they implement a SystemInterface).
39  *
40  * This class keeps track of and prepares the current WebView implementation, and needs to keep
41  * track of a couple of different things such as what package is used as WebView implementation.
42  *
43  * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
44  * thread or on one of multiple Binder threads. The WebView preparation code shares state between
45  * threads meaning that code that chooses a new WebView implementation or checks which
46  * implementation is being used needs to hold a lock.
47  *
48  * The WebViewUpdateService can be accessed in a couple of different ways.
49  * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
50  * as the WebView preparation class.
51  * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
52  * and the WebViewUpdateService should not have been accessed before this call. In this call we
53  * choose WebView implementation for the first time.
54  * 3. The update service listens for Intents related to package installs and removals. These intents
55  * are received and processed on the UI thread. Each intent can result in changing WebView
56  * implementation.
57  * 4. The update service can be reached through Binder calls which are handled on specific binder
58  * threads. These calls can be made from any process. Generally they are used for changing WebView
59  * implementation (from Settings), getting information about the current WebView implementation (for
60  * loading WebView into an app process), or notifying the service about Relro creation being
61  * completed.
62  *
63  * @hide
64  */
65 class WebViewUpdateServiceImpl {
66     private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
67 
68     private static class WebViewPackageMissingException extends Exception {
WebViewPackageMissingException(String message)69         WebViewPackageMissingException(String message) {
70             super(message);
71         }
72 
WebViewPackageMissingException(Exception e)73         WebViewPackageMissingException(Exception e) {
74             super(e);
75         }
76     }
77 
78     private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
79     private static final long NS_PER_MS = 1000000;
80 
81     private static final int VALIDITY_OK = 0;
82     private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
83     private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
84     private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
85     private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
86 
87     private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
88     private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
89 
90     private final SystemInterface mSystemInterface;
91     private final Context mContext;
92 
93     private long mMinimumVersionCode = -1;
94 
95     // Keeps track of the number of running relro creations
96     private int mNumRelroCreationsStarted = 0;
97     private int mNumRelroCreationsFinished = 0;
98     // Implies that we need to rerun relro creation because we are using an out-of-date package
99     private boolean mWebViewPackageDirty = false;
100     private boolean mAnyWebViewInstalled = false;
101 
102     private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
103 
104     // The WebView package currently in use (or the one we are preparing).
105     private PackageInfo mCurrentWebViewPackage = null;
106 
107     private final Object mLock = new Object();
108 
WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface)109     WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
110         mContext = context;
111         mSystemInterface = systemInterface;
112     }
113 
packageStateChanged(String packageName, int changedState, int userId)114     void packageStateChanged(String packageName, int changedState, int userId) {
115         // We don't early out here in different cases where we could potentially early-out (e.g. if
116         // we receive PACKAGE_CHANGED for another user than the system user) since that would
117         // complicate this logic further and open up for more edge cases.
118         for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
119             String webviewPackage = provider.packageName;
120 
121             if (webviewPackage.equals(packageName)) {
122                 boolean updateWebView = false;
123                 boolean removedOrChangedOldPackage = false;
124                 String oldProviderName = null;
125                 PackageInfo newPackage = null;
126                 synchronized (mLock) {
127                     try {
128                         newPackage = findPreferredWebViewPackage();
129                         if (mCurrentWebViewPackage != null) {
130                             oldProviderName = mCurrentWebViewPackage.packageName;
131                         }
132                         // Only trigger update actions if the updated package is the one
133                         // that will be used, or the one that was in use before the
134                         // update, or if we haven't seen a valid WebView package before.
135                         updateWebView =
136                             provider.packageName.equals(newPackage.packageName)
137                             || provider.packageName.equals(oldProviderName)
138                             || mCurrentWebViewPackage == null;
139                         // We removed the old package if we received an intent to remove
140                         // or replace the old package.
141                         removedOrChangedOldPackage =
142                             provider.packageName.equals(oldProviderName);
143                         if (updateWebView) {
144                             onWebViewProviderChanged(newPackage);
145                         }
146                     } catch (WebViewPackageMissingException e) {
147                         mCurrentWebViewPackage = null;
148                         Slog.e(TAG, "Could not find valid WebView package to create relro with "
149                                 + e);
150                     }
151                 }
152                 if (updateWebView && !removedOrChangedOldPackage
153                         && oldProviderName != null) {
154                     // If the provider change is the result of adding or replacing a
155                     // package that was not the previous provider then we must kill
156                     // packages dependent on the old package ourselves. The framework
157                     // only kills dependents of packages that are being removed.
158                     mSystemInterface.killPackageDependents(oldProviderName);
159                 }
160                 return;
161             }
162         }
163     }
164 
prepareWebViewInSystemServer()165     void prepareWebViewInSystemServer() {
166         mSystemInterface.notifyZygote(isMultiProcessEnabled());
167         try {
168             synchronized (mLock) {
169                 mCurrentWebViewPackage = findPreferredWebViewPackage();
170                 String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
171                 if (userSetting != null
172                         && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
173                     // Don't persist the user-chosen setting across boots if the package being
174                     // chosen is not used (could be disabled or uninstalled) so that the user won't
175                     // be surprised by the device switching to using a certain webview package,
176                     // that was uninstalled/disabled a long time ago, if it is installed/enabled
177                     // again.
178                     mSystemInterface.updateUserSetting(mContext,
179                             mCurrentWebViewPackage.packageName);
180                 }
181                 onWebViewProviderChanged(mCurrentWebViewPackage);
182             }
183         } catch (Throwable t) {
184             // Log and discard errors at this stage as we must not crash the system server.
185             Slog.e(TAG, "error preparing webview provider from system server", t);
186         }
187 
188         if (getCurrentWebViewPackage() == null) {
189             // We didn't find a valid WebView implementation. Try explicitly re-enabling the
190             // fallback package for all users in case it was disabled, even if we already did the
191             // one-time migration before. If this actually changes the state, we will see the
192             // PackageManager broadcast shortly and try again.
193             WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
194             WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
195             if (fallbackProvider != null) {
196                 Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
197                 mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
198                                                           true);
199             } else {
200                 Slog.e(TAG, "No valid provider and no fallback available.");
201             }
202         }
203     }
204 
startZygoteWhenReady()205     private void startZygoteWhenReady() {
206         // Wait on a background thread for RELRO creation to be done. We ignore the return value
207         // because even if RELRO creation failed we still want to start the zygote.
208         waitForAndGetProvider();
209         mSystemInterface.ensureZygoteStarted();
210     }
211 
handleNewUser(int userId)212     void handleNewUser(int userId) {
213         // The system user is always started at boot, and by that point we have already run one
214         // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
215         // out here.
216         if (userId == UserHandle.USER_SYSTEM) return;
217         handleUserChange();
218     }
219 
handleUserRemoved(int userId)220     void handleUserRemoved(int userId) {
221         handleUserChange();
222     }
223 
224     /**
225      * Called when a user was added or removed to ensure WebView preparation is triggered.
226      * This has to be done since the WebView package we use depends on the enabled-state
227      * of packages for all users (so adding or removing a user might cause us to change package).
228      */
handleUserChange()229     private void handleUserChange() {
230         // Potentially trigger package-changing logic.
231         updateCurrentWebViewPackage(null);
232     }
233 
notifyRelroCreationCompleted()234     void notifyRelroCreationCompleted() {
235         synchronized (mLock) {
236             mNumRelroCreationsFinished++;
237             checkIfRelrosDoneLocked();
238         }
239     }
240 
waitForAndGetProvider()241     WebViewProviderResponse waitForAndGetProvider() {
242         PackageInfo webViewPackage = null;
243         final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
244         boolean webViewReady = false;
245         int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
246         synchronized (mLock) {
247             webViewReady = webViewIsReadyLocked();
248             while (!webViewReady) {
249                 final long timeNowMs = System.nanoTime() / NS_PER_MS;
250                 if (timeNowMs >= timeoutTimeMs) break;
251                 try {
252                     mLock.wait(timeoutTimeMs - timeNowMs);
253                 } catch (InterruptedException e) {
254                     // ignore
255                 }
256                 webViewReady = webViewIsReadyLocked();
257             }
258             // Make sure we return the provider that was used to create the relro file
259             webViewPackage = mCurrentWebViewPackage;
260             if (webViewReady) {
261                 // success
262             } else if (!mAnyWebViewInstalled) {
263                 webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
264             } else {
265                 // Either the current relro creation  isn't done yet, or the new relro creatioin
266                 // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
267                 webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
268                 Slog.e(TAG, "Timed out waiting for relro creation, relros started "
269                         + mNumRelroCreationsStarted
270                         + " relros finished " + mNumRelroCreationsFinished
271                         + " package dirty? " + mWebViewPackageDirty);
272             }
273         }
274         if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
275         return new WebViewProviderResponse(webViewPackage, webViewStatus);
276     }
277 
278     /**
279      * Change WebView provider and provider setting and kill packages using the old provider.
280      * Return the new provider (in case we are in the middle of creating relro files, or
281      * replacing that provider it will not be in use directly, but will be used when the relros
282      * or the replacement are done).
283      */
changeProviderAndSetting(String newProviderName)284     String changeProviderAndSetting(String newProviderName) {
285         PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
286         if (newPackage == null) return "";
287         return newPackage.packageName;
288     }
289 
290     /**
291      * Update the current WebView package.
292      * @param newProviderName the package to switch to, null if no package has been explicitly
293      * chosen.
294      */
updateCurrentWebViewPackage(@ullable String newProviderName)295     private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
296         PackageInfo oldPackage = null;
297         PackageInfo newPackage = null;
298         boolean providerChanged = false;
299         synchronized (mLock) {
300             oldPackage = mCurrentWebViewPackage;
301 
302             if (newProviderName != null) {
303                 mSystemInterface.updateUserSetting(mContext, newProviderName);
304             }
305 
306             try {
307                 newPackage = findPreferredWebViewPackage();
308                 providerChanged = (oldPackage == null)
309                         || !newPackage.packageName.equals(oldPackage.packageName);
310             } catch (WebViewPackageMissingException e) {
311                 // If updated the Setting but don't have an installed WebView package, the
312                 // Setting will be used when a package is available.
313                 mCurrentWebViewPackage = null;
314                 Slog.e(TAG, "Couldn't find WebView package to use " + e);
315                 return null;
316             }
317             // Perform the provider change if we chose a new provider
318             if (providerChanged) {
319                 onWebViewProviderChanged(newPackage);
320             }
321         }
322         // Kill apps using the old provider only if we changed provider
323         if (providerChanged && oldPackage != null) {
324             mSystemInterface.killPackageDependents(oldPackage.packageName);
325         }
326         // Return the new provider, this is not necessarily the one we were asked to switch to,
327         // but the persistent setting will now be pointing to the provider we were asked to
328         // switch to anyway.
329         return newPackage;
330     }
331 
332     /**
333      * This is called when we change WebView provider, either when the current provider is
334      * updated or a new provider is chosen / takes precedence.
335      */
onWebViewProviderChanged(PackageInfo newPackage)336     private void onWebViewProviderChanged(PackageInfo newPackage) {
337         synchronized (mLock) {
338             mAnyWebViewInstalled = true;
339             if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
340                 mCurrentWebViewPackage = newPackage;
341 
342                 // The relro creations might 'finish' (not start at all) before
343                 // WebViewFactory.onWebViewProviderChanged which means we might not know the
344                 // number of started creations before they finish.
345                 mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
346                 mNumRelroCreationsFinished = 0;
347                 mNumRelroCreationsStarted =
348                     mSystemInterface.onWebViewProviderChanged(newPackage);
349                 // If the relro creations finish before we know the number of started creations
350                 // we will have to do any cleanup/notifying here.
351                 checkIfRelrosDoneLocked();
352             } else {
353                 mWebViewPackageDirty = true;
354             }
355         }
356 
357         // Once we've notified the system that the provider has changed and started RELRO creation,
358         // try to restart the zygote so that it will be ready when apps use it.
359         if (isMultiProcessEnabled()) {
360             AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
361         }
362     }
363 
364     /**
365      * Fetch only the currently valid WebView packages.
366      **/
getValidWebViewPackages()367     WebViewProviderInfo[] getValidWebViewPackages() {
368         ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
369         WebViewProviderInfo[] providers =
370             new WebViewProviderInfo[providersAndPackageInfos.length];
371         for (int n = 0; n < providersAndPackageInfos.length; n++) {
372             providers[n] = providersAndPackageInfos[n].provider;
373         }
374         return providers;
375     }
376 
377     private static class ProviderAndPackageInfo {
378         public final WebViewProviderInfo provider;
379         public final PackageInfo packageInfo;
380 
ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo)381         ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
382             this.provider = provider;
383             this.packageInfo = packageInfo;
384         }
385     }
386 
getValidWebViewPackagesAndInfos()387     private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
388         WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
389         List<ProviderAndPackageInfo> providers = new ArrayList<>();
390         for (int n = 0; n < allProviders.length; n++) {
391             try {
392                 PackageInfo packageInfo =
393                         mSystemInterface.getPackageInfoForProvider(allProviders[n]);
394                 if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
395                     providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
396                 }
397             } catch (NameNotFoundException e) {
398                 // Don't add non-existent packages
399             }
400         }
401         return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
402     }
403 
404     /**
405      * Returns either the package info of the WebView provider determined in the following way:
406      * If the user has chosen a provider then use that if it is valid,
407      * otherwise use the first package in the webview priority list that is valid.
408      *
409      */
findPreferredWebViewPackage()410     private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
411         ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
412 
413         String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
414 
415         // If the user has chosen provider, use that (if it's installed and enabled for all
416         // users).
417         for (ProviderAndPackageInfo providerAndPackage : providers) {
418             if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
419                 // userPackages can contain null objects.
420                 List<UserPackage> userPackages =
421                         mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
422                                 providerAndPackage.provider);
423                 if (isInstalledAndEnabledForAllUsers(userPackages)) {
424                     return providerAndPackage.packageInfo;
425                 }
426             }
427         }
428 
429         // User did not choose, or the choice failed; use the most stable provider that is
430         // installed and enabled for all users, and available by default (not through
431         // user choice).
432         for (ProviderAndPackageInfo providerAndPackage : providers) {
433             if (providerAndPackage.provider.availableByDefault) {
434                 // userPackages can contain null objects.
435                 List<UserPackage> userPackages =
436                         mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
437                                 providerAndPackage.provider);
438                 if (isInstalledAndEnabledForAllUsers(userPackages)) {
439                     return providerAndPackage.packageInfo;
440                 }
441             }
442         }
443 
444         // This should never happen during normal operation (only with modified system images).
445         mAnyWebViewInstalled = false;
446         throw new WebViewPackageMissingException("Could not find a loadable WebView package");
447     }
448 
449     /**
450      * Return true iff {@param packageInfos} point to only installed and enabled packages.
451      * The given packages {@param packageInfos} should all be pointing to the same package, but each
452      * PackageInfo representing a different user's package.
453      */
isInstalledAndEnabledForAllUsers( List<UserPackage> userPackages)454     private static boolean isInstalledAndEnabledForAllUsers(
455             List<UserPackage> userPackages) {
456         for (UserPackage userPackage : userPackages) {
457             if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
458                 return false;
459             }
460         }
461         return true;
462     }
463 
getWebViewPackages()464     WebViewProviderInfo[] getWebViewPackages() {
465         return mSystemInterface.getWebViewPackages();
466     }
467 
getCurrentWebViewPackage()468     PackageInfo getCurrentWebViewPackage() {
469         synchronized (mLock) {
470             return mCurrentWebViewPackage;
471         }
472     }
473 
474     /**
475      * Returns whether WebView is ready and is not going to go through its preparation phase
476      * again directly.
477      */
webViewIsReadyLocked()478     private boolean webViewIsReadyLocked() {
479         return !mWebViewPackageDirty
480             && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
481             // The current package might be replaced though we haven't received an intent
482             // declaring this yet, the following flag makes anyone loading WebView to wait in
483             // this case.
484             && mAnyWebViewInstalled;
485     }
486 
checkIfRelrosDoneLocked()487     private void checkIfRelrosDoneLocked() {
488         if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
489             if (mWebViewPackageDirty) {
490                 mWebViewPackageDirty = false;
491                 // If we have changed provider since we started the relro creation we need to
492                 // redo the whole process using the new package instead.
493                 try {
494                     PackageInfo newPackage = findPreferredWebViewPackage();
495                     onWebViewProviderChanged(newPackage);
496                 } catch (WebViewPackageMissingException e) {
497                     mCurrentWebViewPackage = null;
498                     // If we can't find any valid WebView package we are now in a state where
499                     // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
500                     // should simply wait until we receive an intent declaring a new package was
501                     // installed.
502                 }
503             } else {
504                 mLock.notifyAll();
505             }
506         }
507     }
508 
validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo)509     private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
510         // Ensure the provider targets this framework release (or a later one).
511         if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
512             return VALIDITY_INCORRECT_SDK_VERSION;
513         }
514         if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
515                 && !mSystemInterface.systemIsDebuggable()) {
516             // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
517             // minimum version code. This check is only enforced for user builds.
518             return VALIDITY_INCORRECT_VERSION_CODE;
519         }
520         if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
521             return VALIDITY_INCORRECT_SIGNATURE;
522         }
523         if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
524             return VALIDITY_NO_LIBRARY_FLAG;
525         }
526         return VALIDITY_OK;
527     }
528 
529     /**
530      * Both versionCodes should be from a WebView provider package implemented by Chromium.
531      * VersionCodes from other kinds of packages won't make any sense in this method.
532      *
533      * An introduction to Chromium versionCode scheme:
534      * "BBBBPPPXX"
535      * BBBB: 4 digit branch number. It monotonically increases over time.
536      * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
537      * may change their meaning in the future.
538      * XX: Digits to differentiate different APK builds of the same source version.
539      *
540      * This method takes the "BBBB" of versionCodes and compare them.
541      *
542      * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
543      * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
544      * is the canonical source for how Chromium versionCodes are calculated.
545      *
546      * @return true if versionCode1 is higher than or equal to versionCode2.
547      */
versionCodeGE(long versionCode1, long versionCode2)548     private static boolean versionCodeGE(long versionCode1, long versionCode2) {
549         long v1 = versionCode1 / 100000;
550         long v2 = versionCode2 / 100000;
551 
552         return v1 >= v2;
553     }
554 
555     /**
556      * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
557      * of all available-by-default WebView provider packages. If there is no such WebView provider
558      * package on the system, then return -1, which means all positive versionCode WebView packages
559      * are accepted.
560      *
561      * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
562      * shared between threads. Furthermore, this method does not hold mLock meaning that we must
563      * take extra care to ensure this method is thread-safe.
564      */
getMinimumVersionCode()565     private long getMinimumVersionCode() {
566         if (mMinimumVersionCode > 0) {
567             return mMinimumVersionCode;
568         }
569 
570         long minimumVersionCode = -1;
571         for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
572             if (provider.availableByDefault) {
573                 try {
574                     long versionCode =
575                             mSystemInterface.getFactoryPackageVersion(provider.packageName);
576                     if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
577                         minimumVersionCode = versionCode;
578                     }
579                 } catch (NameNotFoundException e) {
580                     // Safe to ignore.
581                 }
582             }
583         }
584 
585         mMinimumVersionCode = minimumVersionCode;
586         return mMinimumVersionCode;
587     }
588 
providerHasValidSignature(WebViewProviderInfo provider, PackageInfo packageInfo, SystemInterface systemInterface)589     private static boolean providerHasValidSignature(WebViewProviderInfo provider,
590             PackageInfo packageInfo, SystemInterface systemInterface) {
591         // Skip checking signatures on debuggable builds, for development purposes.
592         if (systemInterface.systemIsDebuggable()) return true;
593 
594         // Allow system apps to be valid providers regardless of signature.
595         if (packageInfo.applicationInfo.isSystemApp()) return true;
596 
597         // We don't support packages with multiple signatures.
598         if (packageInfo.signatures.length != 1) return false;
599 
600         // If any of the declared signatures match the package signature, it's valid.
601         for (Signature signature : provider.signatures) {
602             if (signature.equals(packageInfo.signatures[0])) return true;
603         }
604 
605         return false;
606     }
607 
608     /**
609      * Returns the only fallback provider in the set of given packages, or null if there is none.
610      */
getFallbackProvider(WebViewProviderInfo[] webviewPackages)611     private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
612         for (WebViewProviderInfo provider : webviewPackages) {
613             if (provider.isFallback) {
614                 return provider;
615             }
616         }
617         return null;
618     }
619 
isMultiProcessEnabled()620     boolean isMultiProcessEnabled() {
621         int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
622         if (mSystemInterface.isMultiProcessDefaultEnabled()) {
623             // Multiprocess should be enabled unless the user has turned it off manually.
624             return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
625         } else {
626             // Multiprocess should not be enabled, unless the user has turned it on manually.
627             return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
628         }
629     }
630 
enableMultiProcess(boolean enable)631     void enableMultiProcess(boolean enable) {
632         PackageInfo current = getCurrentWebViewPackage();
633         mSystemInterface.setMultiProcessSetting(mContext,
634                 enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
635         mSystemInterface.notifyZygote(enable);
636         if (current != null) {
637             mSystemInterface.killPackageDependents(current.packageName);
638         }
639     }
640 
641     /**
642      * Dump the state of this Service.
643      */
dumpState(PrintWriter pw)644     void dumpState(PrintWriter pw) {
645         pw.println("Current WebView Update Service state");
646         pw.println(String.format("  Multiprocess enabled: %b", isMultiProcessEnabled()));
647         synchronized (mLock) {
648             if (mCurrentWebViewPackage == null) {
649                 pw.println("  Current WebView package is null");
650             } else {
651                 pw.println(String.format("  Current WebView package (name, version): (%s, %s)",
652                         mCurrentWebViewPackage.packageName,
653                         mCurrentWebViewPackage.versionName));
654             }
655             pw.println(String.format("  Minimum targetSdkVersion: %d",
656                     UserPackage.MINIMUM_SUPPORTED_SDK));
657             pw.println(String.format("  Minimum WebView version code: %d",
658                     mMinimumVersionCode));
659             pw.println(String.format("  Number of relros started: %d",
660                     mNumRelroCreationsStarted));
661             pw.println(String.format("  Number of relros finished: %d",
662                         mNumRelroCreationsFinished));
663             pw.println(String.format("  WebView package dirty: %b", mWebViewPackageDirty));
664             pw.println(String.format("  Any WebView package installed: %b",
665                     mAnyWebViewInstalled));
666 
667             try {
668                 PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
669                 pw.println(String.format(
670                         "  Preferred WebView package (name, version): (%s, %s)",
671                         preferredWebViewPackage.packageName,
672                         preferredWebViewPackage.versionName));
673             } catch (WebViewPackageMissingException e) {
674                 pw.println(String.format("  Preferred WebView package: none"));
675             }
676 
677             dumpAllPackageInformationLocked(pw);
678         }
679     }
680 
dumpAllPackageInformationLocked(PrintWriter pw)681     private void dumpAllPackageInformationLocked(PrintWriter pw) {
682         WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
683         pw.println("  WebView packages:");
684         for (WebViewProviderInfo provider : allProviders) {
685             List<UserPackage> userPackages =
686                     mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
687             PackageInfo systemUserPackageInfo =
688                     userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
689             if (systemUserPackageInfo == null) {
690                 pw.println(String.format("    %s is NOT installed.", provider.packageName));
691                 continue;
692             }
693 
694             int validity = validityResult(provider, systemUserPackageInfo);
695             String packageDetails = String.format(
696                     "versionName: %s, versionCode: %d, targetSdkVersion: %d",
697                     systemUserPackageInfo.versionName,
698                     systemUserPackageInfo.getLongVersionCode(),
699                     systemUserPackageInfo.applicationInfo.targetSdkVersion);
700             if (validity == VALIDITY_OK) {
701                 boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
702                         mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
703                 pw.println(String.format(
704                         "    Valid package %s (%s) is %s installed/enabled for all users",
705                         systemUserPackageInfo.packageName,
706                         packageDetails,
707                         installedForAllUsers ? "" : "NOT"));
708             } else {
709                 pw.println(String.format("    Invalid package %s (%s), reason: %s",
710                         systemUserPackageInfo.packageName,
711                         packageDetails,
712                         getInvalidityReason(validity)));
713             }
714         }
715     }
716 
getInvalidityReason(int invalidityReason)717     private static String getInvalidityReason(int invalidityReason) {
718         switch (invalidityReason) {
719             case VALIDITY_INCORRECT_SDK_VERSION:
720                 return "SDK version too low";
721             case VALIDITY_INCORRECT_VERSION_CODE:
722                 return "Version code too low";
723             case VALIDITY_INCORRECT_SIGNATURE:
724                 return "Incorrect signature";
725             case VALIDITY_NO_LIBRARY_FLAG:
726                 return "No WebView-library manifest flag";
727             default:
728                 return "Unexcepted validity-reason";
729         }
730     }
731 }
732