WebViewUpdateServiceImpl.java revision 95f7e8e06dd0003a813f7a4b3c718c68d0fa4a01
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 */
16package com.android.server.webkit;
17
18import android.content.Context;
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageInfo;
21import android.content.pm.PackageManager.NameNotFoundException;
22import android.content.pm.Signature;
23import android.util.Base64;
24import android.util.Slog;
25import android.webkit.WebViewFactory;
26import android.webkit.WebViewProviderInfo;
27import android.webkit.WebViewProviderResponse;
28
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.List;
32
33/**
34 * Implementation of the WebViewUpdateService.
35 * This class doesn't depend on the android system like the actual Service does and can be used
36 * directly by tests (as long as they implement a SystemInterface).
37 * @hide
38 */
39public class WebViewUpdateServiceImpl {
40    private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
41
42    private SystemInterface mSystemInterface;
43    private WebViewUpdater mWebViewUpdater;
44    private Context mContext;
45
46    public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
47        mContext = context;
48        mSystemInterface = systemInterface;
49        mWebViewUpdater = new WebViewUpdater(mContext, mSystemInterface);
50    }
51
52    void packageStateChanged(String packageName, int changedState) {
53        updateFallbackStateOnPackageChange(packageName, changedState);
54        mWebViewUpdater.packageStateChanged(packageName, changedState);
55    }
56
57    void prepareWebViewInSystemServer() {
58        updateFallbackStateOnBoot();
59        mWebViewUpdater.prepareWebViewInSystemServer();
60    }
61
62    private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
63        for (WebViewProviderInfo provider : providers) {
64            if (provider.availableByDefault && !provider.isFallback) {
65                try {
66                    PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
67                    if (isEnabledPackage(packageInfo)
68                            && mWebViewUpdater.isValidProvider(provider, packageInfo)) {
69                        return true;
70                    }
71                } catch (NameNotFoundException e) {
72                    // A non-existent provider is neither valid nor enabled
73                }
74            }
75        }
76        return false;
77    }
78
79    /**
80     * Called when a new user has been added to update the state of its fallback package.
81     */
82    void handleNewUser(int userId) {
83        if (!mSystemInterface.isFallbackLogicEnabled()) return;
84
85        WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
86        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
87        if (fallbackProvider == null) return;
88
89        mSystemInterface.enablePackageForUser(fallbackProvider.packageName,
90                !existsValidNonFallbackProvider(webviewProviders), userId);
91    }
92
93    void notifyRelroCreationCompleted() {
94        mWebViewUpdater.notifyRelroCreationCompleted();
95    }
96
97    WebViewProviderResponse waitForAndGetProvider() {
98        return mWebViewUpdater.waitForAndGetProvider();
99    }
100
101    String changeProviderAndSetting(String newProvider) {
102        return mWebViewUpdater.changeProviderAndSetting(newProvider);
103    }
104
105    WebViewProviderInfo[] getValidWebViewPackages() {
106        return mWebViewUpdater.getValidWebViewPackages();
107    }
108
109    WebViewProviderInfo[] getWebViewPackages() {
110        return mSystemInterface.getWebViewPackages();
111    }
112
113    String getCurrentWebViewPackageName() {
114        return mWebViewUpdater.getCurrentWebViewPackageName();
115    }
116
117    void enableFallbackLogic(boolean enable) {
118        mSystemInterface.enableFallbackLogic(enable);
119    }
120
121    private void updateFallbackStateOnBoot() {
122        if (!mSystemInterface.isFallbackLogicEnabled()) return;
123
124        WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
125        updateFallbackState(webviewProviders, true);
126    }
127
128    /**
129     * Handle the enabled-state of our fallback package, i.e. if there exists some non-fallback
130     * package that is valid (and available by default) then disable the fallback package,
131     * otherwise, enable the fallback package.
132     */
133    private void updateFallbackStateOnPackageChange(String changedPackage, int changedState) {
134        if (!mSystemInterface.isFallbackLogicEnabled()) return;
135
136        WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
137
138        // A package was changed / updated / downgraded, early out if it is not one of the
139        // webview packages that are available by default.
140        boolean changedPackageAvailableByDefault = false;
141        for (WebViewProviderInfo provider : webviewProviders) {
142            if (provider.packageName.equals(changedPackage)) {
143                if (provider.availableByDefault) {
144                    changedPackageAvailableByDefault = true;
145                }
146                break;
147            }
148        }
149        if (!changedPackageAvailableByDefault) return;
150        updateFallbackState(webviewProviders, false);
151    }
152
153    private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) {
154        // If there exists a valid and enabled non-fallback package - disable the fallback
155        // package, otherwise, enable it.
156        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
157        if (fallbackProvider == null) return;
158        boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);
159
160        boolean isFallbackEnabled = false;
161        try {
162            isFallbackEnabled = isEnabledPackage(
163                    mSystemInterface.getPackageInfoForProvider(fallbackProvider));
164        } catch (NameNotFoundException e) {
165            // No fallback package installed -> early out.
166            return;
167        }
168
169        if (existsValidNonFallbackProvider
170                // During an OTA the primary user's WebView state might differ from other users', so
171                // ignore the state of that user during boot.
172                && (isFallbackEnabled || isBoot)) {
173            mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext,
174                    fallbackProvider.packageName);
175        } else if (!existsValidNonFallbackProvider
176                // During an OTA the primary user's WebView state might differ from other users', so
177                // ignore the state of that user during boot.
178                && (!isFallbackEnabled || isBoot)) {
179            // Enable the fallback package for all users.
180            mSystemInterface.enablePackageForAllUsers(mContext,
181                    fallbackProvider.packageName, true);
182        }
183    }
184
185    /**
186     * Returns the only fallback provider in the set of given packages, or null if there is none.
187     */
188    private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
189        for (WebViewProviderInfo provider : webviewPackages) {
190            if (provider.isFallback) {
191                return provider;
192            }
193        }
194        return null;
195    }
196
197    boolean isFallbackPackage(String packageName) {
198        if (packageName == null || !mSystemInterface.isFallbackLogicEnabled()) return false;
199
200        WebViewProviderInfo[] webviewPackages = mSystemInterface.getWebViewPackages();
201        WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewPackages);
202        return (fallbackProvider != null
203                && packageName.equals(fallbackProvider.packageName));
204    }
205
206    /**
207     * Class that decides what WebView implementation to use and prepares that implementation for
208     * use.
209     */
210    private static class WebViewUpdater {
211        private Context mContext;
212        private SystemInterface mSystemInterface;
213        private int mMinimumVersionCode = -1;
214
215        public WebViewUpdater(Context context, SystemInterface systemInterface) {
216            mContext = context;
217            mSystemInterface = systemInterface;
218        }
219
220        private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000.
221
222        // Keeps track of the number of running relro creations
223        private int mNumRelroCreationsStarted = 0;
224        private int mNumRelroCreationsFinished = 0;
225        // Implies that we need to rerun relro creation because we are using an out-of-date package
226        private boolean mWebViewPackageDirty = false;
227        private boolean mAnyWebViewInstalled = false;
228
229        private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
230
231        // The WebView package currently in use (or the one we are preparing).
232        private PackageInfo mCurrentWebViewPackage = null;
233
234        private Object mLock = new Object();
235
236        public void packageStateChanged(String packageName, int changedState) {
237            for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
238                String webviewPackage = provider.packageName;
239
240                if (webviewPackage.equals(packageName)) {
241                    boolean updateWebView = false;
242                    boolean removedOrChangedOldPackage = false;
243                    String oldProviderName = null;
244                    PackageInfo newPackage = null;
245                    synchronized(mLock) {
246                        try {
247                            newPackage = findPreferredWebViewPackage();
248                            if (mCurrentWebViewPackage != null) {
249                                oldProviderName = mCurrentWebViewPackage.packageName;
250                                if (changedState == WebViewUpdateService.PACKAGE_CHANGED
251                                        && newPackage.packageName.equals(oldProviderName)) {
252                                    // If we don't change package name we should only rerun the
253                                    // preparation phase if the current package has been replaced
254                                    // (not if it has been enabled/disabled).
255                                    return;
256                                }
257                            }
258                            // Only trigger update actions if the updated package is the one
259                            // that will be used, or the one that was in use before the
260                            // update, or if we haven't seen a valid WebView package before.
261                            updateWebView =
262                                provider.packageName.equals(newPackage.packageName)
263                                || provider.packageName.equals(oldProviderName)
264                                || mCurrentWebViewPackage == null;
265                            // We removed the old package if we received an intent to remove
266                            // or replace the old package.
267                            removedOrChangedOldPackage =
268                                provider.packageName.equals(oldProviderName);
269                            if (updateWebView) {
270                                onWebViewProviderChanged(newPackage);
271                            }
272                        } catch (WebViewFactory.MissingWebViewPackageException e) {
273                            Slog.e(TAG, "Could not find valid WebView package to create " +
274                                    "relro with " + e);
275                        }
276                    }
277                    if(updateWebView && !removedOrChangedOldPackage
278                            && oldProviderName != null) {
279                        // If the provider change is the result of adding or replacing a
280                        // package that was not the previous provider then we must kill
281                        // packages dependent on the old package ourselves. The framework
282                        // only kills dependents of packages that are being removed.
283                        mSystemInterface.killPackageDependents(oldProviderName);
284                    }
285                    return;
286                }
287            }
288        }
289
290        public void prepareWebViewInSystemServer() {
291            try {
292                synchronized(mLock) {
293                    mCurrentWebViewPackage = findPreferredWebViewPackage();
294                    // Don't persist the user-chosen setting across boots if the package being
295                    // chosen is not used (could be disabled or uninstalled) so that the user won't
296                    // be surprised by the device switching to using a certain webview package,
297                    // that was uninstalled/disabled a long time ago, if it is installed/enabled
298                    // again.
299                    mSystemInterface.updateUserSetting(mContext,
300                            mCurrentWebViewPackage.packageName);
301                    onWebViewProviderChanged(mCurrentWebViewPackage);
302                }
303            } catch (Throwable t) {
304                // Log and discard errors at this stage as we must not crash the system server.
305                Slog.e(TAG, "error preparing webview provider from system server", t);
306            }
307        }
308
309        /**
310         * Change WebView provider and provider setting and kill packages using the old provider.
311         * Return the new provider (in case we are in the middle of creating relro files, or
312         * replacing that provider it will not be in use directly, but will be used when the relros
313         * or the replacement are done).
314         */
315        public String changeProviderAndSetting(String newProviderName) {
316            PackageInfo oldPackage = null;
317            PackageInfo newPackage = null;
318            boolean providerChanged = false;
319            synchronized(mLock) {
320                oldPackage = mCurrentWebViewPackage;
321                mSystemInterface.updateUserSetting(mContext, newProviderName);
322
323                try {
324                    newPackage = findPreferredWebViewPackage();
325                    providerChanged = (oldPackage == null)
326                            || !newPackage.packageName.equals(oldPackage.packageName);
327                } catch (WebViewFactory.MissingWebViewPackageException e) {
328                    Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " +
329                            "package " + e);
330                    // If we don't perform the user change but don't have an installed WebView
331                    // package, we will have changed the setting and it will be used when a package
332                    // is available.
333                    return "";
334                }
335                // Perform the provider change if we chose a new provider
336                if (providerChanged) {
337                    onWebViewProviderChanged(newPackage);
338                }
339            }
340            // Kill apps using the old provider only if we changed provider
341            if (providerChanged && oldPackage != null) {
342                mSystemInterface.killPackageDependents(oldPackage.packageName);
343            }
344            // Return the new provider, this is not necessarily the one we were asked to switch to
345            // But the persistent setting will now be pointing to the provider we were asked to
346            // switch to anyway
347            return newPackage.packageName;
348        }
349
350        /**
351         * This is called when we change WebView provider, either when the current provider is
352         * updated or a new provider is chosen / takes precedence.
353         */
354        private void onWebViewProviderChanged(PackageInfo newPackage) {
355            synchronized(mLock) {
356                mAnyWebViewInstalled = true;
357                if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
358                    mCurrentWebViewPackage = newPackage;
359
360                    // The relro creations might 'finish' (not start at all) before
361                    // WebViewFactory.onWebViewProviderChanged which means we might not know the
362                    // number of started creations before they finish.
363                    mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
364                    mNumRelroCreationsFinished = 0;
365                    mNumRelroCreationsStarted =
366                        mSystemInterface.onWebViewProviderChanged(newPackage);
367                    // If the relro creations finish before we know the number of started creations
368                    // we will have to do any cleanup/notifying here.
369                    checkIfRelrosDoneLocked();
370                } else {
371                    mWebViewPackageDirty = true;
372                }
373            }
374        }
375
376        private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
377            WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
378            List<ProviderAndPackageInfo> providers = new ArrayList<>();
379            for(int n = 0; n < allProviders.length; n++) {
380                try {
381                    PackageInfo packageInfo =
382                        mSystemInterface.getPackageInfoForProvider(allProviders[n]);
383                    if (isValidProvider(allProviders[n], packageInfo)) {
384                        providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
385                    }
386                } catch (NameNotFoundException e) {
387                    // Don't add non-existent packages
388                }
389            }
390            return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
391        }
392
393        /**
394         * Fetch only the currently valid WebView packages.
395         **/
396        public WebViewProviderInfo[] getValidWebViewPackages() {
397            ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
398            WebViewProviderInfo[] providers =
399                new WebViewProviderInfo[providersAndPackageInfos.length];
400            for(int n = 0; n < providersAndPackageInfos.length; n++) {
401                providers[n] = providersAndPackageInfos[n].provider;
402            }
403            return providers;
404        }
405
406
407        private class ProviderAndPackageInfo {
408            public final WebViewProviderInfo provider;
409            public final PackageInfo packageInfo;
410
411            public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
412                this.provider = provider;
413                this.packageInfo = packageInfo;
414            }
415        }
416
417        /**
418         * Returns either the package info of the WebView provider determined in the following way:
419         * If the user has chosen a provider then use that if it is valid,
420         * otherwise use the first package in the webview priority list that is valid.
421         *
422         */
423        private PackageInfo findPreferredWebViewPackage() {
424            ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
425
426            String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
427
428            // If the user has chosen provider, use that
429            for (ProviderAndPackageInfo providerAndPackage : providers) {
430                if (providerAndPackage.provider.packageName.equals(userChosenProvider)
431                        && isEnabledPackage(providerAndPackage.packageInfo)) {
432                    return providerAndPackage.packageInfo;
433                }
434            }
435
436            // User did not choose, or the choice failed; use the most stable provider that is
437            // enabled and available by default (not through user choice).
438            for (ProviderAndPackageInfo providerAndPackage : providers) {
439                if (providerAndPackage.provider.availableByDefault
440                        && isEnabledPackage(providerAndPackage.packageInfo)) {
441                    return providerAndPackage.packageInfo;
442                }
443            }
444
445            // Could not find any enabled package either, use the most stable and default-available
446            // provider.
447            for (ProviderAndPackageInfo providerAndPackage : providers) {
448                if (providerAndPackage.provider.availableByDefault) {
449                    return providerAndPackage.packageInfo;
450                }
451            }
452
453            mAnyWebViewInstalled = false;
454            throw new WebViewFactory.MissingWebViewPackageException(
455                    "Could not find a loadable WebView package");
456        }
457
458        public void notifyRelroCreationCompleted() {
459            synchronized (mLock) {
460                mNumRelroCreationsFinished++;
461                checkIfRelrosDoneLocked();
462            }
463        }
464
465        public WebViewProviderResponse waitForAndGetProvider() {
466            PackageInfo webViewPackage = null;
467            final long NS_PER_MS = 1000000;
468            final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
469            boolean webViewReady = false;
470            int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
471            synchronized (mLock) {
472                webViewReady = webViewIsReadyLocked();
473                while (!webViewReady) {
474                    final long timeNowMs = System.nanoTime() / NS_PER_MS;
475                    if (timeNowMs >= timeoutTimeMs) break;
476                    try {
477                        mLock.wait(timeoutTimeMs - timeNowMs);
478                    } catch (InterruptedException e) {}
479                    webViewReady = webViewIsReadyLocked();
480                }
481                // Make sure we return the provider that was used to create the relro file
482                webViewPackage = mCurrentWebViewPackage;
483                if (webViewReady) {
484                } else if (!mAnyWebViewInstalled) {
485                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
486                } else {
487                    // Either the current relro creation  isn't done yet, or the new relro creatioin
488                    // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
489                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
490                }
491            }
492            if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
493            return new WebViewProviderResponse(webViewPackage, webViewStatus);
494        }
495
496        public String getCurrentWebViewPackageName() {
497            synchronized(mLock) {
498                if (mCurrentWebViewPackage == null)
499                    return null;
500                return mCurrentWebViewPackage.packageName;
501            }
502        }
503
504        /**
505         * Returns whether WebView is ready and is not going to go through its preparation phase
506         * again directly.
507         */
508        private boolean webViewIsReadyLocked() {
509            return !mWebViewPackageDirty
510                && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
511                // The current package might be replaced though we haven't received an intent
512                // declaring this yet, the following flag makes anyone loading WebView to wait in
513                // this case.
514                && mAnyWebViewInstalled;
515        }
516
517        private void checkIfRelrosDoneLocked() {
518            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
519                if (mWebViewPackageDirty) {
520                    mWebViewPackageDirty = false;
521                    // If we have changed provider since we started the relro creation we need to
522                    // redo the whole process using the new package instead.
523                    try {
524                        PackageInfo newPackage = findPreferredWebViewPackage();
525                        onWebViewProviderChanged(newPackage);
526                    } catch (WebViewFactory.MissingWebViewPackageException e) {
527                        // If we can't find any valid WebView package we are now in a state where
528                        // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
529                        // should simply wait until we receive an intent declaring a new package was
530                        // installed.
531                    }
532                } else {
533                    mLock.notifyAll();
534                }
535            }
536        }
537
538        /**
539         * Returns whether this provider is valid for use as a WebView provider.
540         */
541        public boolean isValidProvider(WebViewProviderInfo configInfo,
542                PackageInfo packageInfo) {
543            if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
544                    && packageInfo.versionCode < getMinimumVersionCode()
545                    && !mSystemInterface.systemIsDebuggable()) {
546                // Non-system package webview providers may be downgraded arbitrarily low, prevent
547                // that by enforcing minimum version code. This check is only enforced for user
548                // builds.
549                return false;
550            }
551            if (providerHasValidSignature(configInfo, packageInfo, mSystemInterface) &&
552                    WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) {
553                return true;
554            }
555            return false;
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 and non-fallback WebView provider packages. If there is no
561         * such WebView provider package on the system, then return -1, which means all positive
562         * versionCode WebView packages are accepted.
563         */
564        private int getMinimumVersionCode() {
565            if (mMinimumVersionCode > 0) {
566                return mMinimumVersionCode;
567            }
568
569            for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
570                if (provider.availableByDefault && !provider.isFallback) {
571                    try {
572                        int versionCode =
573                            mSystemInterface.getFactoryPackageVersion(provider.packageName);
574                        if (mMinimumVersionCode < 0 || versionCode < mMinimumVersionCode) {
575                            mMinimumVersionCode = versionCode;
576                        }
577                    } catch (NameNotFoundException e) {
578                        // Safe to ignore.
579                    }
580                }
581            }
582
583            return mMinimumVersionCode;
584        }
585    }
586
587    private static boolean providerHasValidSignature(WebViewProviderInfo provider,
588            PackageInfo packageInfo, SystemInterface systemInterface) {
589        if (systemInterface.systemIsDebuggable()) {
590            return true;
591        }
592        Signature[] packageSignatures;
593        // If no signature is declared, instead check whether the package is included in the
594        // system.
595        if (provider.signatures == null || provider.signatures.length == 0) {
596            return packageInfo.applicationInfo.isSystemApp();
597        }
598        packageSignatures = packageInfo.signatures;
599        if (packageSignatures.length != 1)
600            return false;
601
602        final byte[] packageSignature = packageSignatures[0].toByteArray();
603        // Return whether the package signature matches any of the valid signatures
604        for (String signature : provider.signatures) {
605            final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT);
606            if (Arrays.equals(packageSignature, validSignature))
607                return true;
608        }
609        return false;
610    }
611
612    /**
613     * Returns whether the given package is enabled.
614     * This state can be changed by the user from Settings->Apps
615     */
616    private static boolean isEnabledPackage(PackageInfo packageInfo) {
617        return packageInfo.applicationInfo.enabled;
618    }
619
620}
621