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