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