WebViewUpdateService.java revision 7a002a215db498d232100c0b227d145244a03412
1/*
2 * Copyright (C) 2012 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
17package com.android.server.webkit;
18
19import android.app.ActivityManagerNative;
20import android.app.AppGlobals;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.Signature;
29import android.os.Binder;
30import android.os.Process;
31import android.os.RemoteException;
32import android.os.UserHandle;
33import android.provider.Settings;
34import android.provider.Settings.Global;
35import android.util.AndroidRuntimeException;
36import android.util.Slog;
37import android.webkit.IWebViewUpdateService;
38import android.webkit.WebViewProviderInfo;
39import android.webkit.WebViewProviderResponse;
40import android.webkit.WebViewFactory;
41
42import com.android.server.SystemService;
43
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Iterator;
47import java.util.List;
48
49/**
50 * Private service to wait for the updatable WebView to be ready for use.
51 * @hide
52 */
53public class WebViewUpdateService extends SystemService {
54
55    private static final String TAG = "WebViewUpdateService";
56    private static final int WAIT_TIMEOUT_MS = 4500; // KEY_DISPATCHING_TIMEOUT is 5000.
57
58    // Keeps track of the number of running relro creations
59    private int mNumRelroCreationsStarted = 0;
60    private int mNumRelroCreationsFinished = 0;
61    // Implies that we need to rerun relro creation because we are using an out-of-date package
62    private boolean mWebViewPackageDirty = false;
63    // Set to true when the current provider is being replaced
64    private boolean mCurrentProviderBeingReplaced = false;
65    private boolean mAnyWebViewInstalled = false;
66
67    private int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
68
69    // The WebView package currently in use (or the one we are preparing).
70    private PackageInfo mCurrentWebViewPackage = null;
71    // The WebView providers that are currently available.
72    private WebViewProviderInfo[] mCurrentValidWebViewPackages = null;
73
74    private BroadcastReceiver mWebViewUpdatedReceiver;
75
76    public WebViewUpdateService(Context context) {
77        super(context);
78    }
79
80    @Override
81    public void onStart() {
82        mWebViewUpdatedReceiver = new BroadcastReceiver() {
83                @Override
84                public void onReceive(Context context, Intent intent) {
85                    // When a package is replaced we will receive two intents, one representing
86                    // the removal of the old package and one representing the addition of the
87                    // new package.
88                    // In the case where we receive an intent to remove the old version of the
89                    // package that is being replaced we set a flag here and early-out so that we
90                    // don't change provider while replacing the current package (we will instead
91                    // change provider when the new version of the package is being installed).
92                    if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
93                        && intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) {
94                        synchronized(WebViewUpdateService.this) {
95                            if (mCurrentWebViewPackage == null) return;
96
97                            String webViewPackage = "package:" + mCurrentWebViewPackage.packageName;
98                            if (webViewPackage.equals(intent.getDataString()))
99                                mCurrentProviderBeingReplaced = true;
100                        }
101
102                        return;
103                    }
104
105                    for (WebViewProviderInfo provider : WebViewFactory.getWebViewPackages()) {
106                        String webviewPackage = "package:" + provider.packageName;
107
108                        if (webviewPackage.equals(intent.getDataString())) {
109                            boolean updateWebView = false;
110                            boolean removedOldPackage = false;
111                            String oldProviderName = null;
112                            PackageInfo newPackage = null;
113                            synchronized(WebViewUpdateService.this) {
114                                try {
115                                    updateValidWebViewPackages();
116                                    newPackage = findPreferredWebViewPackage();
117                                    if (mCurrentWebViewPackage != null)
118                                        oldProviderName = mCurrentWebViewPackage.packageName;
119                                    // Only trigger update actions if the updated package is the one
120                                    // that will be used, or the one that was in use before the
121                                    // update, or if we haven't seen a valid WebView package before.
122                                    updateWebView =
123                                        provider.packageName.equals(newPackage.packageName)
124                                        || provider.packageName.equals(oldProviderName)
125                                        || mCurrentWebViewPackage == null;
126                                    // We removed the old package if we received an intent to remove
127                                    // or replace the old package.
128                                    removedOldPackage =
129                                        provider.packageName.equals(oldProviderName);
130                                    if (updateWebView) {
131                                        onWebViewProviderChanged(newPackage);
132                                    }
133                                } catch (WebViewFactory.MissingWebViewPackageException e) {
134                                    Slog.e(TAG, "Could not find valid WebView package to create " +
135                                            "relro with " + e);
136                                }
137                            }
138                            if(updateWebView && !removedOldPackage && oldProviderName != null) {
139                                // If the provider change is the result of adding or replacing a
140                                // package that was not the previous provider then we must kill
141                                // packages dependent on the old package ourselves. The framework
142                                // only kills dependents of packages that are being removed.
143                                try {
144                                    ActivityManagerNative.getDefault().killPackageDependents(
145                                        oldProviderName, UserHandle.USER_ALL);
146                                } catch (RemoteException e) {
147                                }
148                            }
149                            return;
150                        }
151                    }
152                }
153        };
154        IntentFilter filter = new IntentFilter();
155        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
156        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
157        filter.addDataScheme("package");
158        getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
159
160        publishBinderService("webviewupdate", new BinderService());
161    }
162
163    /**
164     * Perform any WebView loading preparations that must happen at boot from the system server,
165     * after the package manager has started or after an update to the webview is installed.
166     * This must be called in the system server.
167     * Currently, this means spawning the child processes which will create the relro files.
168     */
169    public void prepareWebViewInSystemServer() {
170        try {
171            synchronized(this) {
172                updateValidWebViewPackages();
173                mCurrentWebViewPackage = findPreferredWebViewPackage();
174                onWebViewProviderChanged(mCurrentWebViewPackage);
175            }
176        } catch (Throwable t) {
177            // Log and discard errors at this stage as we must not crash the system server.
178            Slog.e(TAG, "error preparing webview provider from system server", t);
179        }
180    }
181
182
183    /**
184     * Change WebView provider and provider setting and kill packages using the old provider.
185     */
186    private void changeProviderAndSetting(String newProviderName) {
187        PackageInfo oldPackage = null;
188        PackageInfo newPackage = null;
189        synchronized(this) {
190            oldPackage = mCurrentWebViewPackage;
191            updateUserSetting(newProviderName);
192
193            try {
194                newPackage = findPreferredWebViewPackage();
195                if (oldPackage != null && newPackage.packageName.equals(oldPackage.packageName)) {
196                    // If we don't perform the user change, revert the settings change.
197                    updateUserSetting(newPackage.packageName);
198                    return;
199                }
200            } catch (WebViewFactory.MissingWebViewPackageException e) {
201                Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView package "
202                        + e);
203                // If we don't perform the user change but don't have an installed WebView package,
204                // we will have changed the setting and it will be used when a package is available.
205                return;
206            }
207            onWebViewProviderChanged(newPackage);
208        }
209        // Kill apps using the old provider
210        try {
211            if (oldPackage != null) {
212                ActivityManagerNative.getDefault().killPackageDependents(
213                        oldPackage.packageName, UserHandle.USER_ALL);
214            }
215        } catch (RemoteException e) {
216        }
217        return;
218    }
219
220    /**
221     * This is called when we change WebView provider, either when the current provider is updated
222     * or a new provider is chosen / takes precedence.
223     */
224    private void onWebViewProviderChanged(PackageInfo newPackage) {
225        synchronized(this) {
226            mAnyWebViewInstalled = true;
227            // If we have changed provider then the replacement of the old provider is
228            // irrelevant - we can only have chosen a new provider if its package is available.
229            mCurrentProviderBeingReplaced = false;
230            if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
231                mCurrentWebViewPackage = newPackage;
232                updateUserSetting(newPackage.packageName);
233
234                // The relro creations might 'finish' (not start at all) before
235                // WebViewFactory.onWebViewProviderChanged which means we might not know the number
236                // of started creations before they finish.
237                mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
238                mNumRelroCreationsFinished = 0;
239                mNumRelroCreationsStarted = WebViewFactory.onWebViewProviderChanged(newPackage);
240                // If the relro creations finish before we know the number of started creations we
241                // will have to do any cleanup/notifying here.
242                checkIfRelrosDoneLocked();
243            } else {
244                mWebViewPackageDirty = true;
245            }
246        }
247    }
248
249    /**
250     * Updates the currently valid WebView provider packages.
251     * Should be used when a provider has been installed or removed.
252     * @hide
253     * */
254    private void updateValidWebViewPackages() {
255        List<WebViewProviderInfo> webViewProviders  =
256            new ArrayList<WebViewProviderInfo>(Arrays.asList(WebViewFactory.getWebViewPackages()));
257        Iterator<WebViewProviderInfo> it = webViewProviders.iterator();
258        // remove non-valid packages
259        while(it.hasNext()) {
260            WebViewProviderInfo current = it.next();
261            if (!current.isValidProvider())
262                it.remove();
263        }
264        synchronized(this) {
265            mCurrentValidWebViewPackages =
266                webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
267        }
268    }
269
270    private static String getUserChosenWebViewProvider() {
271        return Settings.Global.getString(AppGlobals.getInitialApplication().getContentResolver(),
272                Settings.Global.WEBVIEW_PROVIDER);
273    }
274
275    private void updateUserSetting(String newProviderName) {
276        Settings.Global.putString(getContext().getContentResolver(),
277                Settings.Global.WEBVIEW_PROVIDER,
278                newProviderName == null ? "" : newProviderName);
279    }
280
281    /**
282     * Returns either the package info of the WebView provider determined in the following way:
283     * If the user has chosen a provider then use that if it is valid,
284     * otherwise use the first package in the webview priority list that is valid.
285     *
286     * @hide
287     */
288    private PackageInfo findPreferredWebViewPackage() {
289        WebViewProviderInfo[] providers = mCurrentValidWebViewPackages;
290
291        String userChosenProvider = getUserChosenWebViewProvider();
292
293        // If the user has chosen provider, use that
294        for (WebViewProviderInfo provider : providers) {
295            if (provider.packageName.equals(userChosenProvider) && provider.isEnabled()) {
296                return provider.getPackageInfo();
297            }
298        }
299
300        // User did not choose, or the choice failed; use the most stable provider that is
301        // enabled and available by default (not through user choice).
302        for (WebViewProviderInfo provider : providers) {
303            if (provider.isAvailableByDefault() && provider.isEnabled()) {
304                return provider.getPackageInfo();
305            }
306        }
307
308        // Could not find any enabled package either, use the most stable provider.
309        for (WebViewProviderInfo provider : providers) {
310            return provider.getPackageInfo();
311        }
312
313        mAnyWebViewInstalled = false;
314        throw new WebViewFactory.MissingWebViewPackageException(
315                "Could not find a loadable WebView package");
316    }
317
318    /**
319     * Returns whether WebView is ready and is not going to go through its preparation phase again
320     * directly.
321     */
322    private boolean webViewIsReadyLocked() {
323        return !mWebViewPackageDirty
324            && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
325            && !mCurrentProviderBeingReplaced
326            // The current package might be replaced though we haven't received an intent declaring
327            // this yet, the following flag makes anyone loading WebView to wait in this case.
328            && mAnyWebViewInstalled;
329    }
330
331    private void checkIfRelrosDoneLocked() {
332        if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
333            if (mWebViewPackageDirty) {
334                mWebViewPackageDirty = false;
335                // If we have changed provider since we started the relro creation we need to
336                // redo the whole process using the new package instead.
337                // Though, if the current provider package is being replaced we don't want to change
338                // provider here since we will perform the change either when the package is added
339                // again or when we switch to another provider (whichever comes first).
340                if (!mCurrentProviderBeingReplaced) {
341                    PackageInfo newPackage = findPreferredWebViewPackage();
342                    onWebViewProviderChanged(newPackage);
343                }
344            } else {
345                this.notifyAll();
346            }
347        }
348    }
349
350    private class BinderService extends IWebViewUpdateService.Stub {
351
352        /**
353         * The shared relro process calls this to notify us that it's done trying to create a relro
354         * file. This method gets called even if the relro creation has failed or the process
355         * crashed.
356         */
357        @Override // Binder call
358        public void notifyRelroCreationCompleted() {
359            // Verify that the caller is either the shared relro process (nominal case) or the
360            // system server (only in the case the relro process crashes and we get here via the
361            // crashHandler).
362            if (Binder.getCallingUid() != Process.SHARED_RELRO_UID &&
363                    Binder.getCallingUid() != Process.SYSTEM_UID) {
364                return;
365            }
366
367            synchronized (WebViewUpdateService.this) {
368                mNumRelroCreationsFinished++;
369                checkIfRelrosDoneLocked();
370            }
371        }
372
373        /**
374         * WebViewFactory calls this to block WebView loading until the relro file is created.
375         * Returns the WebView provider for which we create relro files.
376         */
377        @Override // Binder call
378        public WebViewProviderResponse waitForAndGetProvider() {
379            // The WebViewUpdateService depends on the prepareWebViewInSystemServer call, which
380            // happens later (during the PHASE_ACTIVITY_MANAGER_READY) in SystemServer.java. If
381            // another service there tries to bring up a WebView in the between, the wait below
382            // would deadlock without the check below.
383            if (Binder.getCallingPid() == Process.myPid()) {
384                throw new IllegalStateException("Cannot create a WebView from the SystemServer");
385            }
386
387            PackageInfo webViewPackage = null;
388            final long NS_PER_MS = 1000000;
389            final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
390            boolean webViewReady = false;
391            int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
392            synchronized (WebViewUpdateService.this) {
393                webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
394                while (!webViewReady) {
395                    final long timeNowMs = System.nanoTime() / NS_PER_MS;
396                    if (timeNowMs >= timeoutTimeMs) break;
397                    try {
398                        WebViewUpdateService.this.wait(timeoutTimeMs - timeNowMs);
399                    } catch (InterruptedException e) {}
400                    webViewReady = WebViewUpdateService.this.webViewIsReadyLocked();
401                }
402                // Make sure we return the provider that was used to create the relro file
403                webViewPackage = WebViewUpdateService.this.mCurrentWebViewPackage;
404                if (webViewReady) {
405                } else if (mCurrentProviderBeingReplaced) {
406                    // It is important that we check this flag before the one representing WebView
407                    // being installed, otherwise we might think there is no WebView though the
408                    // current one is just being replaced.
409                    webViewStatus = WebViewFactory.LIBLOAD_WEBVIEW_BEING_REPLACED;
410                } else if (!mAnyWebViewInstalled) {
411                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
412                } else {
413                    // Either the current relro creation  isn't done yet, or the new relro creatioin
414                    // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
415                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
416                }
417            }
418            if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
419            return new WebViewProviderResponse(webViewPackage, webViewStatus);
420        }
421
422        /**
423         * This is called from DeveloperSettings when the user changes WebView provider.
424         */
425        @Override // Binder call
426        public void changeProviderAndSetting(String newProvider) {
427            if (getContext().checkCallingPermission(
428                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
429                    != PackageManager.PERMISSION_GRANTED) {
430                String msg = "Permission Denial: changeProviderAndSetting() from pid="
431                        + Binder.getCallingPid()
432                        + ", uid=" + Binder.getCallingUid()
433                        + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
434                Slog.w(TAG, msg);
435                throw new SecurityException(msg);
436            }
437
438            WebViewUpdateService.this.changeProviderAndSetting(newProvider);
439        }
440
441        @Override // Binder call
442        public WebViewProviderInfo[] getValidWebViewPackages() {
443            synchronized(WebViewUpdateService.this) {
444                return mCurrentValidWebViewPackages;
445            }
446        }
447
448        @Override // Binder call
449        public String getCurrentWebViewPackageName() {
450            synchronized(WebViewUpdateService.this) {
451                if (WebViewUpdateService.this.mCurrentWebViewPackage == null)
452                    return null;
453                return WebViewUpdateService.this.mCurrentWebViewPackage.packageName;
454            }
455        }
456    }
457}
458