content: /*
   * Copyright (C) 2012 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.webkit;
  
  import android.annotation.SystemApi;
  import android.annotation.UnsupportedAppUsage;
  import android.app.ActivityManager;
  import android.app.AppGlobals;
  import android.app.Application;
  import android.content.Context;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.Signature;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.os.Trace;
  import android.util.AndroidRuntimeException;
  import android.util.ArraySet;
  import android.util.Log;
  
  import java.io.File;
  import java.lang.reflect.Method;
  
  /**
   * Top level factory, used creating all the main WebView implementation classes.
   *
   * @hide
   */
  @SystemApi
  public final class WebViewFactory {
  
      // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
      /** @hide */
      private static final String CHROMIUM_WEBVIEW_FACTORY =
              "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";
  
      private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
  
      private static final String LOGTAG = "WebViewFactory";
  
      private static final boolean DEBUG = false;
  
      // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
      // same provider.
      @UnsupportedAppUsage
      private static WebViewFactoryProvider sProviderInstance;
      private static final Object sProviderLock = new Object();
      @UnsupportedAppUsage
      private static PackageInfo sPackageInfo;
      private static Boolean sWebViewSupported;
      private static boolean sWebViewDisabled;
      private static String sDataDirectorySuffix; // stored here so it can be set without loading WV
  
      // Error codes for loadWebViewNativeLibraryFromPackage
      public static final int LIBLOAD_SUCCESS = 0;
      public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
      public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
  
      // error codes for waiting for WebView preparation
      public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
      public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
  
      // native relro loading error codes
      public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
      public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
      public static final int LIBLOAD_FAILED_JNI_CALL = 7;
  
      // more error codes for waiting for WebView preparation
      public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8;
  
      // error for namespace lookup
      public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
  
  
      private static String getWebViewPreparationErrorReason(int error) {
          switch (error) {
              case LIBLOAD_FAILED_WAITING_FOR_RELRO:
                  return "Time out waiting for Relro files being created";
              case LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES:
                  return "No WebView installed";
              case LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN:
                  return "Crashed for unknown reason";
          }
          return "Unknown";
      }
  
      static class MissingWebViewPackageException extends Exception {
          public MissingWebViewPackageException(String message) { super(message); }
          public MissingWebViewPackageException(Exception e) { super(e); }
      }
  
      private static boolean isWebViewSupported() {
          // No lock; this is a benign race as Boolean's state is final and the PackageManager call
          // will always return the same value.
          if (sWebViewSupported == null) {
              sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
                      .hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
          }
          return sWebViewSupported;
      }
  
      /**
       * @hide
       */
      static void disableWebView() {
          synchronized (sProviderLock) {
              if (sProviderInstance != null) {
                  throw new IllegalStateException(
                          "Can't disable WebView: WebView already initialized");
              }
              sWebViewDisabled = true;
          }
      }
  
      /**
       * @hide
       */
      static void setDataDirectorySuffix(String suffix) {
          synchronized (sProviderLock) {
              if (sProviderInstance != null) {
                  throw new IllegalStateException(
                          "Can't set data directory suffix: WebView already initialized");
              }
              if (suffix.indexOf(File.separatorChar) >= 0) {
                  throw new IllegalArgumentException("Suffix " + suffix
                                                     + " contains a path separator");
              }
              sDataDirectorySuffix = suffix;
          }
      }
  
      /**
       * @hide
       */
      static String getDataDirectorySuffix() {
          synchronized (sProviderLock) {
              return sDataDirectorySuffix;
          }
      }
  
      /**
       * @hide
       */
      public static String getWebViewLibrary(ApplicationInfo ai) {
          if (ai.metaData != null)
              return ai.metaData.getString("com.android.webview.WebViewLibrary");
          return null;
      }
  
      public static PackageInfo getLoadedPackageInfo() {
          synchronized (sProviderLock) {
              return sPackageInfo;
          }
      }
  
      /**
       * @hide
       */
      public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
              throws ClassNotFoundException {
          return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
                  true, clazzLoader);
      }
  
      /**
       * Load the native library for the given package name if that package
       * name is the same as the one providing the webview.
       */
      public static int loadWebViewNativeLibraryFromPackage(String packageName,
                                                            ClassLoader clazzLoader) {
          if (!isWebViewSupported()) {
              return LIBLOAD_WRONG_PACKAGE_NAME;
          }
  
          WebViewProviderResponse response = null;
          try {
              response = getUpdateService().waitForAndGetProvider();
          } catch (RemoteException e) {
              Log.e(LOGTAG, "error waiting for relro creation", e);
              return LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN;
          }
  
  
          if (response.status != LIBLOAD_SUCCESS
                  && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
              return response.status;
          }
          if (!response.packageInfo.packageName.equals(packageName)) {
              return LIBLOAD_WRONG_PACKAGE_NAME;
          }
  
          PackageManager packageManager = AppGlobals.getInitialApplication().getPackageManager();
          String libraryFileName;
          try {
              PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
                      PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
              libraryFileName = getWebViewLibrary(packageInfo.applicationInfo);
          } catch (PackageManager.NameNotFoundException e) {
              Log.e(LOGTAG, "Couldn't find package " + packageName);
              return LIBLOAD_WRONG_PACKAGE_NAME;
          }
  
          int loadNativeRet = WebViewLibraryLoader.loadNativeLibrary(clazzLoader, libraryFileName);
          // If we failed waiting for relro we want to return that fact even if we successfully
          // load the relro file.
          if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
          return loadNativeRet;
      }
  
      @UnsupportedAppUsage
      static WebViewFactoryProvider getProvider() {
          synchronized (sProviderLock) {
              // For now the main purpose of this function (and the factory abstraction) is to keep
              // us honest and minimize usage of WebView internals when binding the proxy.
              if (sProviderInstance != null) return sProviderInstance;
  
              final int uid = android.os.Process.myUid();
              if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                      || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
                      || uid == android.os.Process.BLUETOOTH_UID) {
                  throw new UnsupportedOperationException(
                          "For security reasons, WebView is not allowed in privileged processes");
              }
  
              if (!isWebViewSupported()) {
                  // Device doesn't support WebView; don't try to load it, just throw.
                  throw new UnsupportedOperationException();
              }
  
              if (sWebViewDisabled) {
                  throw new IllegalStateException(
                          "WebView.disableWebView() was called: WebView is disabled");
              }
  
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
              try {
                  Class<WebViewFactoryProvider> providerClass = getProviderClass();
                  Method staticFactory = null;
                  try {
                      staticFactory = providerClass.getMethod(
                          CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
                  } catch (Exception e) {
                      if (DEBUG) {
                          Log.w(LOGTAG, "error instantiating provider with static factory method", e);
                      }
                  }
  
                  Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
                  try {
                      sProviderInstance = (WebViewFactoryProvider)
                              staticFactory.invoke(null, new WebViewDelegate());
                      if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                      return sProviderInstance;
                  } catch (Exception e) {
                      Log.e(LOGTAG, "error instantiating provider", e);
                      throw new AndroidRuntimeException(e);
                  } finally {
                      Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                  }
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
          }
      }
  
      /**
       * Returns {@code true} if the signatures match, {@code false} otherwise
       */
      private static boolean signaturesEquals(Signature[] s1, Signature[] s2) {
          if (s1 == null) {
              return s2 == null;
          }
          if (s2 == null) return false;
  
          ArraySet<Signature> set1 = new ArraySet<>();
          for(Signature signature : s1) {
              set1.add(signature);
          }
          ArraySet<Signature> set2 = new ArraySet<>();
          for(Signature signature : s2) {
              set2.add(signature);
          }
          return set1.equals(set2);
      }
  
      // Throws MissingWebViewPackageException on failure
      private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)
              throws MissingWebViewPackageException {
          if (!chosen.packageName.equals(toUse.packageName)) {
              throw new MissingWebViewPackageException("Failed to verify WebView provider, "
                      + "packageName mismatch, expected: "
                      + chosen.packageName + " actual: " + toUse.packageName);
          }
          if (chosen.getLongVersionCode() > toUse.getLongVersionCode()) {
              throw new MissingWebViewPackageException("Failed to verify WebView provider, "
                      + "version code is lower than expected: " + chosen.getLongVersionCode()
                      + " actual: " + toUse.getLongVersionCode());
          }
          if (getWebViewLibrary(toUse.applicationInfo) == null) {
              throw new MissingWebViewPackageException("Tried to load an invalid WebView provider: "
                      + toUse.packageName);
          }
          if (!signaturesEquals(chosen.signatures, toUse.signatures)) {
              throw new MissingWebViewPackageException("Failed to verify WebView provider, "
                      + "signature mismatch");
          }
      }
  
      @UnsupportedAppUsage
      private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
          Application initialApplication = AppGlobals.getInitialApplication();
          try {
              WebViewProviderResponse response = null;
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                      "WebViewUpdateService.waitForAndGetProvider()");
              try {
                  response = getUpdateService().waitForAndGetProvider();
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
              if (response.status != LIBLOAD_SUCCESS
                      && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
                  throw new MissingWebViewPackageException("Failed to load WebView provider: "
                          + getWebViewPreparationErrorReason(response.status));
              }
              // Register to be killed before fetching package info - so that we will be
              // killed if the package info goes out-of-date.
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "ActivityManager.addPackageDependency()");
              try {
                  ActivityManager.getService().addPackageDependency(
                          response.packageInfo.packageName);
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
              // Fetch package info and verify it against the chosen package
              PackageInfo newPackageInfo = null;
              PackageManager pm = initialApplication.getPackageManager();
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
              try {
                  newPackageInfo = pm.getPackageInfo(
                      response.packageInfo.packageName,
                      PackageManager.GET_SHARED_LIBRARY_FILES
                      | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
                      // Make sure that we fetch the current provider even if its not
                      // installed for the current user
                      | PackageManager.MATCH_UNINSTALLED_PACKAGES
                      // Fetch signatures for verification
                      | PackageManager.GET_SIGNATURES
                      // Get meta-data for meta data flag verification
                      | PackageManager.GET_META_DATA);
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
  
              // Validate the newly fetched package info, throws MissingWebViewPackageException on
              // failure
              verifyPackageInfo(response.packageInfo, newPackageInfo);
  
              ApplicationInfo ai = newPackageInfo.applicationInfo;
  
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                      "initialApplication.createApplicationContext");
              try {
                  // Construct an app context to load the Java code into the current app.
                  Context webViewContext = initialApplication.createApplicationContext(
                          ai,
                          Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
                  sPackageInfo = newPackageInfo;
                  return webViewContext;
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
          } catch (RemoteException | PackageManager.NameNotFoundException e) {
              throw new MissingWebViewPackageException("Failed to load WebView provider: " + e);
          }
      }
  
      @UnsupportedAppUsage
      private static Class<WebViewFactoryProvider> getProviderClass() {
          Context webViewContext = null;
          Application initialApplication = AppGlobals.getInitialApplication();
  
          try {
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                      "WebViewFactory.getWebViewContextAndSetProvider()");
              try {
                  webViewContext = getWebViewContextAndSetProvider();
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
              Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
                      sPackageInfo.versionName + " (code " + sPackageInfo.getLongVersionCode() + ")");
  
              Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
              try {
                  for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
                      initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
                  }
                  ClassLoader clazzLoader = webViewContext.getClassLoader();
  
                  Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                  WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                          getWebViewLibrary(sPackageInfo.applicationInfo));
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
  
                  Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
                  try {
                      return getWebViewProviderClass(clazzLoader);
                  } finally {
                      Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                  }
              } catch (ClassNotFoundException e) {
                  Log.e(LOGTAG, "error loading provider", e);
                  throw new AndroidRuntimeException(e);
              } finally {
                  Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
              }
          } catch (MissingWebViewPackageException e) {
              Log.e(LOGTAG, "Chromium WebView package does not exist", e);
              throw new AndroidRuntimeException(e);
          }
      }
  
      /**
       * Perform any WebView loading preparations that must happen in the zygote.
       * Currently, this means allocating address space to load the real JNI library later.
       */
      public static void prepareWebViewInZygote() {
          try {
              WebViewLibraryLoader.reserveAddressSpaceInZygote();
          } catch (Throwable t) {
              // Log and discard errors at this stage as we must not crash the zygote.
              Log.e(LOGTAG, "error preparing native loader", t);
          }
      }
  
      /**
       * @hide
       */
      public static int onWebViewProviderChanged(PackageInfo packageInfo) {
          int startedRelroProcesses = 0;
          try {
              startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
          } catch (Throwable t) {
              // Log and discard errors at this stage as we must not crash the system server.
              Log.e(LOGTAG, "error preparing webview native library", t);
          }
  
          WebViewZygote.onWebViewProviderChanged(packageInfo);
  
          return startedRelroProcesses;
      }
  
      private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
  
      /** @hide */
      @UnsupportedAppUsage
      public static IWebViewUpdateService getUpdateService() {
          if (isWebViewSupported()) {
              return getUpdateServiceUnchecked();
          } else {
              return null;
          }
      }
  
      /** @hide */
      static IWebViewUpdateService getUpdateServiceUnchecked() {
          return IWebViewUpdateService.Stub.asInterface(
                  ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
      }
  }
  