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