Home | History | Annotate | Download | only in webkit
      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 
     17 package com.android.server.webkit;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.os.Binder;
     26 import android.os.PatternMatcher;
     27 import android.os.Process;
     28 import android.os.ResultReceiver;
     29 import android.os.ShellCallback;
     30 import android.os.UserHandle;
     31 import android.util.Slog;
     32 import android.webkit.IWebViewUpdateService;
     33 import android.webkit.WebViewProviderInfo;
     34 import android.webkit.WebViewProviderResponse;
     35 
     36 import com.android.internal.util.DumpUtils;
     37 import com.android.server.SystemService;
     38 
     39 import java.io.FileDescriptor;
     40 import java.io.PrintWriter;
     41 import java.util.Arrays;
     42 
     43 /**
     44  * Private service to wait for the updatable WebView to be ready for use.
     45  * @hide
     46  */
     47 public class WebViewUpdateService extends SystemService {
     48 
     49     private static final String TAG = "WebViewUpdateService";
     50 
     51     private BroadcastReceiver mWebViewUpdatedReceiver;
     52     private WebViewUpdateServiceImpl mImpl;
     53 
     54     static final int PACKAGE_CHANGED = 0;
     55     static final int PACKAGE_ADDED = 1;
     56     static final int PACKAGE_ADDED_REPLACED = 2;
     57     static final int PACKAGE_REMOVED = 3;
     58 
     59     public WebViewUpdateService(Context context) {
     60         super(context);
     61         mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
     62     }
     63 
     64     @Override
     65     public void onStart() {
     66         mWebViewUpdatedReceiver = new BroadcastReceiver() {
     67                 @Override
     68                 public void onReceive(Context context, Intent intent) {
     69                     int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
     70                     switch (intent.getAction()) {
     71                         case Intent.ACTION_PACKAGE_REMOVED:
     72                             // When a package is replaced we will receive two intents, one
     73                             // representing the removal of the old package and one representing the
     74                             // addition of the new package.
     75                             // In the case where we receive an intent to remove the old version of
     76                             // the package that is being replaced we early-out here so that we don't
     77                             // run the update-logic twice.
     78                             if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return;
     79                             mImpl.packageStateChanged(packageNameFromIntent(intent),
     80                                     PACKAGE_REMOVED, userId);
     81                             break;
     82                         case Intent.ACTION_PACKAGE_CHANGED:
     83                             // Ensure that we only heed PACKAGE_CHANGED intents if they change an
     84                             // entire package, not just a component
     85                             if (entirePackageChanged(intent)) {
     86                                 mImpl.packageStateChanged(packageNameFromIntent(intent),
     87                                         PACKAGE_CHANGED, userId);
     88                             }
     89                             break;
     90                         case Intent.ACTION_PACKAGE_ADDED:
     91                             mImpl.packageStateChanged(packageNameFromIntent(intent),
     92                                     (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)
     93                                      ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED), userId);
     94                             break;
     95                         case Intent.ACTION_USER_STARTED:
     96                             mImpl.handleNewUser(userId);
     97                             break;
     98                         case Intent.ACTION_USER_REMOVED:
     99                             mImpl.handleUserRemoved(userId);
    100                             break;
    101                     }
    102                 }
    103         };
    104         IntentFilter filter = new IntentFilter();
    105         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
    106         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    107         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    108         filter.addDataScheme("package");
    109         // Make sure we only receive intents for WebView packages from our config file.
    110         for (WebViewProviderInfo provider : mImpl.getWebViewPackages()) {
    111             filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
    112         }
    113 
    114         getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, filter,
    115                 null /* broadcast permission */, null /* handler */);
    116 
    117         IntentFilter userAddedFilter = new IntentFilter();
    118         userAddedFilter.addAction(Intent.ACTION_USER_STARTED);
    119         userAddedFilter.addAction(Intent.ACTION_USER_REMOVED);
    120         getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
    121                 userAddedFilter, null /* broadcast permission */, null /* handler */);
    122 
    123         publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
    124     }
    125 
    126     public void prepareWebViewInSystemServer() {
    127         mImpl.prepareWebViewInSystemServer();
    128     }
    129 
    130     private static String packageNameFromIntent(Intent intent) {
    131         return intent.getDataString().substring("package:".length());
    132     }
    133 
    134     /**
    135      * Returns whether the entire package from an ACTION_PACKAGE_CHANGED intent was changed (rather
    136      * than just one of its components).
    137      * @hide
    138      */
    139     public static boolean entirePackageChanged(Intent intent) {
    140         String[] componentList =
    141             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
    142         return Arrays.asList(componentList).contains(
    143                 intent.getDataString().substring("package:".length()));
    144     }
    145 
    146     private class BinderService extends IWebViewUpdateService.Stub {
    147 
    148         @Override
    149         public void onShellCommand(FileDescriptor in, FileDescriptor out,
    150                 FileDescriptor err, String[] args, ShellCallback callback,
    151                 ResultReceiver resultReceiver) {
    152             (new WebViewUpdateServiceShellCommand(this)).exec(
    153                     this, in, out, err, args, callback, resultReceiver);
    154         }
    155 
    156 
    157         /**
    158          * The shared relro process calls this to notify us that it's done trying to create a relro
    159          * file. This method gets called even if the relro creation has failed or the process
    160          * crashed.
    161          */
    162         @Override // Binder call
    163         public void notifyRelroCreationCompleted() {
    164             // Verify that the caller is either the shared relro process (nominal case) or the
    165             // system server (only in the case the relro process crashes and we get here via the
    166             // crashHandler).
    167             if (Binder.getCallingUid() != Process.SHARED_RELRO_UID &&
    168                     Binder.getCallingUid() != Process.SYSTEM_UID) {
    169                 return;
    170             }
    171 
    172             long callingId = Binder.clearCallingIdentity();
    173             try {
    174                 WebViewUpdateService.this.mImpl.notifyRelroCreationCompleted();
    175             } finally {
    176                 Binder.restoreCallingIdentity(callingId);
    177             }
    178         }
    179 
    180         /**
    181          * WebViewFactory calls this to block WebView loading until the relro file is created.
    182          * Returns the WebView provider for which we create relro files.
    183          */
    184         @Override // Binder call
    185         public WebViewProviderResponse waitForAndGetProvider() {
    186             // The WebViewUpdateService depends on the prepareWebViewInSystemServer call, which
    187             // happens later (during the PHASE_ACTIVITY_MANAGER_READY) in SystemServer.java. If
    188             // another service there tries to bring up a WebView in the between, the wait below
    189             // would deadlock without the check below.
    190             if (Binder.getCallingPid() == Process.myPid()) {
    191                 throw new IllegalStateException("Cannot create a WebView from the SystemServer");
    192             }
    193 
    194             return WebViewUpdateService.this.mImpl.waitForAndGetProvider();
    195         }
    196 
    197         /**
    198          * This is called from DeveloperSettings when the user changes WebView provider.
    199          */
    200         @Override // Binder call
    201         public String changeProviderAndSetting(String newProvider) {
    202             if (getContext().checkCallingPermission(
    203                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
    204                     != PackageManager.PERMISSION_GRANTED) {
    205                 String msg = "Permission Denial: changeProviderAndSetting() from pid="
    206                         + Binder.getCallingPid()
    207                         + ", uid=" + Binder.getCallingUid()
    208                         + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
    209                 Slog.w(TAG, msg);
    210                 throw new SecurityException(msg);
    211             }
    212 
    213             long callingId = Binder.clearCallingIdentity();
    214             try {
    215                 return WebViewUpdateService.this.mImpl.changeProviderAndSetting(
    216                         newProvider);
    217             } finally {
    218                 Binder.restoreCallingIdentity(callingId);
    219             }
    220         }
    221 
    222         @Override // Binder call
    223         public WebViewProviderInfo[] getValidWebViewPackages() {
    224             return WebViewUpdateService.this.mImpl.getValidWebViewPackages();
    225         }
    226 
    227         @Override // Binder call
    228         public WebViewProviderInfo[] getAllWebViewPackages() {
    229             return WebViewUpdateService.this.mImpl.getWebViewPackages();
    230         }
    231 
    232         @Override // Binder call
    233         public String getCurrentWebViewPackageName() {
    234             PackageInfo pi = WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
    235             return pi == null ? null : pi.packageName;
    236         }
    237 
    238         @Override // Binder call
    239         public PackageInfo getCurrentWebViewPackage() {
    240             return WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
    241         }
    242 
    243         @Override // Binder call
    244         public boolean isFallbackPackage(String packageName) {
    245             return WebViewUpdateService.this.mImpl.isFallbackPackage(packageName);
    246         }
    247 
    248         @Override // Binder call
    249         public void enableFallbackLogic(boolean enable) {
    250             if (getContext().checkCallingPermission(
    251                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
    252                     != PackageManager.PERMISSION_GRANTED) {
    253                 String msg = "Permission Denial: enableFallbackLogic() from pid="
    254                         + Binder.getCallingPid()
    255                         + ", uid=" + Binder.getCallingUid()
    256                         + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
    257                 Slog.w(TAG, msg);
    258                 throw new SecurityException(msg);
    259             }
    260 
    261             long callingId = Binder.clearCallingIdentity();
    262             try {
    263                 WebViewUpdateService.this.mImpl.enableFallbackLogic(enable);
    264             } finally {
    265                 Binder.restoreCallingIdentity(callingId);
    266             }
    267         }
    268 
    269         @Override // Binder call
    270         public boolean isMultiProcessEnabled() {
    271             return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
    272         }
    273 
    274         @Override // Binder call
    275         public void enableMultiProcess(boolean enable) {
    276             if (getContext().checkCallingPermission(
    277                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
    278                     != PackageManager.PERMISSION_GRANTED) {
    279                 String msg = "Permission Denial: enableMultiProcess() from pid="
    280                         + Binder.getCallingPid()
    281                         + ", uid=" + Binder.getCallingUid()
    282                         + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
    283                 Slog.w(TAG, msg);
    284                 throw new SecurityException(msg);
    285             }
    286 
    287             long callingId = Binder.clearCallingIdentity();
    288             try {
    289                 WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
    290             } finally {
    291                 Binder.restoreCallingIdentity(callingId);
    292             }
    293         }
    294 
    295         @Override
    296         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    297             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
    298             WebViewUpdateService.this.mImpl.dumpState(pw);
    299         }
    300     }
    301 }
    302