Commit 9d4aa096 authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: adds versioning

This patch separates out creation of WebLayer to a WebLayerFactory and
has the client supply the version of chrome the client was compiled with.
WebLayerFactory also offers functions to return the version of the
implementation. This does not change the existing versioning checks, rather
adds the real chrome version. The goal is we will eventually use the real
chrome version and not the one of version we added.

BUG=1025615
TEST=all tests implicitly trigger the new code paths

Change-Id: Ie6330a57fd9f205504758c9cde9707537cb643f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1935037
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: default avatarClark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#720825}
parent ff9eac7c
...@@ -51,6 +51,7 @@ android_library("java") { ...@@ -51,6 +51,7 @@ android_library("java") {
"org/chromium/weblayer_private/TabImpl.java", "org/chromium/weblayer_private/TabImpl.java",
"org/chromium/weblayer_private/TopControlsContainerView.java", "org/chromium/weblayer_private/TopControlsContainerView.java",
"org/chromium/weblayer_private/WebContentsGestureStateTracker.java", "org/chromium/weblayer_private/WebContentsGestureStateTracker.java",
"org/chromium/weblayer_private/WebLayerFactoryImpl.java",
"org/chromium/weblayer_private/WebLayerImpl.java", "org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/metrics/UmaUtils.java", "org/chromium/weblayer_private/metrics/UmaUtils.java",
] ]
...@@ -172,5 +173,6 @@ android_aidl("aidl") { ...@@ -172,5 +173,6 @@ android_aidl("aidl") {
"org/chromium/weblayer_private/interfaces/ITab.aidl", "org/chromium/weblayer_private/interfaces/ITab.aidl",
"org/chromium/weblayer_private/interfaces/ITabClient.aidl", "org/chromium/weblayer_private/interfaces/ITabClient.aidl",
"org/chromium/weblayer_private/interfaces/IWebLayer.aidl", "org/chromium/weblayer_private/interfaces/IWebLayer.aidl",
"org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl",
] ]
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private;
import android.os.IBinder;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.components.version_info.VersionConstants;
import org.chromium.weblayer_private.interfaces.IWebLayer;
import org.chromium.weblayer_private.interfaces.IWebLayerFactory;
import org.chromium.weblayer_private.interfaces.WebLayerVersion;
/**
* Factory used to create WebLayer as well as verify compatibility.
* This is constructed by the client library using reflection.
*/
@UsedByReflection("WebLayer")
public final class WebLayerFactoryImpl extends IWebLayerFactory.Stub {
private final int mClientMajorVersion;
private final String mClientVersion;
private final int mClientWeblayerVersion;
/**
* This function is called by the client using reflection.
*
* @param clientVersion The full version string the client was compiled from.
* @param clientMajorVersion The major version number the client was compiled from. This is also
* contained in clientVersion.
* @param clientWebLayerVersion The version from interfaces.WebLayerVersion the client was
* compiled with.
*/
@UsedByReflection("WebLayer")
public static IBinder create(
String clientVersion, int clientMajorVersion, int clientWebLayerVersion) {
return new WebLayerFactoryImpl(clientVersion, clientMajorVersion, clientWebLayerVersion);
}
private WebLayerFactoryImpl(
String clientVersion, int clientMajorVersion, int clientWeblayerVersion) {
mClientMajorVersion = clientMajorVersion;
mClientVersion = clientVersion;
mClientWeblayerVersion = clientWeblayerVersion;
}
/**
* Returns true if the client compiled with the specific version is compatible with this
* implementation. The client library calls this exactly once.
*/
@Override
public boolean isClientSupported() {
return mClientWeblayerVersion == WebLayerVersion.sVersionNumber;
}
/**
* Returns the major version of the implementation.
*/
@Override
public int getImplementationMajorVersion() {
return VersionConstants.PRODUCT_MAJOR_VERSION;
}
/**
* Returns the full version string of the implementation.
*/
@Override
public String getImplementationVersion() {
return VersionConstants.PRODUCT_VERSION;
}
@Override
public IWebLayer createWebLayer() {
assert isClientSupported();
return new WebLayerImpl();
}
}
...@@ -12,7 +12,6 @@ import android.content.res.AssetManager; ...@@ -12,7 +12,6 @@ import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.util.SparseArray; import android.util.SparseArray;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -28,7 +27,6 @@ import org.chromium.base.PathUtils; ...@@ -28,7 +27,6 @@ import org.chromium.base.PathUtils;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType; import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory; import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory;
...@@ -43,7 +41,6 @@ import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; ...@@ -43,7 +41,6 @@ import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient;
import org.chromium.weblayer_private.interfaces.IWebLayer; import org.chromium.weblayer_private.interfaces.IWebLayer;
import org.chromium.weblayer_private.interfaces.ObjectWrapper; import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import org.chromium.weblayer_private.interfaces.WebLayerVersion;
import org.chromium.weblayer_private.metrics.UmaUtils; import org.chromium.weblayer_private.metrics.UmaUtils;
import java.io.File; import java.io.File;
...@@ -52,10 +49,8 @@ import java.lang.reflect.Method; ...@@ -52,10 +49,8 @@ import java.lang.reflect.Method;
/** /**
* Root implementation class for WebLayer. * Root implementation class for WebLayer.
* This is constructed by the client library using reflection.
*/ */
@JNINamespace("weblayer") @JNINamespace("weblayer")
@UsedByReflection("WebLayer")
public final class WebLayerImpl extends IWebLayer.Stub { public final class WebLayerImpl extends IWebLayer.Stub {
// TODO: should there be one tag for all this code? // TODO: should there be one tag for all this code?
private static final String TAG = "WebLayer"; private static final String TAG = "WebLayer";
...@@ -80,20 +75,7 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -80,20 +75,7 @@ public final class WebLayerImpl extends IWebLayer.Stub {
} }
} }
@UsedByReflection("WebLayer") WebLayerImpl() {}
public static IBinder create() {
return new WebLayerImpl();
}
private WebLayerImpl() {}
/**
* Returns true if the client and implementation versions are compatible.
*/
@UsedByReflection("WebLayer")
public static boolean checkVersion(int clientVersion) {
return clientVersion == WebLayerVersion.sVersionNumber;
}
@Override @Override
public void loadAsync(IObjectWrapper appContextWrapper, IObjectWrapper loadedCallbackWrapper) { public void loadAsync(IObjectWrapper appContextWrapper, IObjectWrapper loadedCallbackWrapper) {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private.interfaces;
import android.os.Bundle;
import org.chromium.weblayer_private.interfaces.IWebLayer;
// Factory for creating IWebLayer as well as determining if a particular version
// of a client is supported.
interface IWebLayerFactory {
// Returns true if a client with the specified version is supported.
boolean isClientSupported() = 0;
// Creates a new IWebLayer. It is expected that a client has a single
// IWebLayer. Further, at this time, only a single client is supported.
IWebLayer createWebLayer() = 1;
// Returns the full version string of the implementation.
String getImplementationVersion() = 2;
// Returns the major version of the implementation.
int getImplementationMajorVersion() = 3;
}
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
import("//build/config/android/config.gni") import("//build/config/android/config.gni")
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
import("//build/util/process_version.gni")
_version_constants_java_file =
"$target_gen_dir/org/chromium/weblayer/WebLayerClientVersionConstants.java"
weblayer_client_manifest = weblayer_client_manifest =
"$target_gen_dir/weblayer_client_manifest/AndroidManifest.xml" "$target_gen_dir/weblayer_client_manifest/AndroidManifest.xml"
...@@ -47,10 +51,12 @@ android_library("java") { ...@@ -47,10 +51,12 @@ android_library("java") {
"org/chromium/weblayer/UnsupportedVersionException.java", "org/chromium/weblayer/UnsupportedVersionException.java",
"org/chromium/weblayer/WebLayer.java", "org/chromium/weblayer/WebLayer.java",
"org/chromium/weblayer/WebLayerFileProvider.java", "org/chromium/weblayer/WebLayerFileProvider.java",
_version_constants_java_file,
] ]
deps = [ deps = [
":client_resources", ":client_resources",
":client_version",
":weblayer_client_manifest", ":weblayer_client_manifest",
"//third_party/android_deps:androidx_annotation_annotation_java", "//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:com_android_support_support_compat_java", "//third_party/android_deps:com_android_support_support_compat_java",
...@@ -87,3 +93,13 @@ dist_aar("client_aar") { ...@@ -87,3 +93,13 @@ dist_aar("client_aar") {
android_manifest = weblayer_client_manifest android_manifest = weblayer_client_manifest
output = "$root_build_dir/WebLayerClient.aar" output = "$root_build_dir/WebLayerClient.aar"
} }
process_version("client_version") {
process_only = true
template_file =
"org/chromium/weblayer/WebLayerClientVersionConstants.java.version"
output = _version_constants_java_file
sources = [
"//chrome/VERSION",
]
}
...@@ -7,12 +7,13 @@ package org.chromium.weblayer; ...@@ -7,12 +7,13 @@ package org.chromium.weblayer;
/** /**
* Error thrown if client and implementation versions are not compatible. * Error thrown if client and implementation versions are not compatible.
*/ */
public class UnsupportedVersionException extends Exception { public class UnsupportedVersionException extends RuntimeException {
/** /**
* Constructs a new exception with the specified version. * Constructs a new exception with the specified version.
*/ */
public UnsupportedVersionException(int clientVersion) { public UnsupportedVersionException(String implementationVersion) {
super("Unsupported WebLayer version, client version " + clientVersion super("Unsupported WebLayer version, client version "
+ " is not supported by the implementation."); + WebLayerClientVersionConstants.PRODUCT_VERSION
+ " is not supported by implementation version " + implementationVersion);
} }
} }
...@@ -14,6 +14,7 @@ import android.os.Process; ...@@ -14,6 +14,7 @@ import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.AndroidRuntimeException; import android.util.AndroidRuntimeException;
import android.util.Log;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
...@@ -25,6 +26,7 @@ import org.chromium.weblayer_private.interfaces.IBrowserFragment; ...@@ -25,6 +26,7 @@ import org.chromium.weblayer_private.interfaces.IBrowserFragment;
import org.chromium.weblayer_private.interfaces.IProfile; import org.chromium.weblayer_private.interfaces.IProfile;
import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient;
import org.chromium.weblayer_private.interfaces.IWebLayer; import org.chromium.weblayer_private.interfaces.IWebLayer;
import org.chromium.weblayer_private.interfaces.IWebLayerFactory;
import org.chromium.weblayer_private.interfaces.ObjectWrapper; import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.interfaces.WebLayerVersion; import org.chromium.weblayer_private.interfaces.WebLayerVersion;
...@@ -53,48 +55,44 @@ public final class WebLayer { ...@@ -53,48 +55,44 @@ public final class WebLayer {
private final IWebLayer mImpl; private final IWebLayer mImpl;
/** /**
* Loads the WebLayer implementation and returns the IWebLayer. This does *not* trigger the * Returns true if WebLayer is available. This tries to load WebLayer, but does no
* implementation to start. * initialization. This function may be called by code that uses WebView.
* <p>
* NOTE: it's possible for this to return true, yet loading to still fail. This happens if there
* is an error during loading.
*
* @return true Returns true if WebLayer is available.
*/ */
private static IWebLayer connectToWebLayerImplementation(@NonNull Context appContext) public static boolean isAvailable(Context context) {
throws UnsupportedVersionException { ThreadCheck.ensureOnUiThread();
ClassLoader remoteClassLoader; context = context.getApplicationContext();
try { return getWebLayerLoader(context).isAvailable();
remoteClassLoader = getOrCreateRemoteClassLoader(appContext); }
} catch (Exception e) {
throw new AndroidRuntimeException(e);
}
try {
Class webLayerClass =
remoteClassLoader.loadClass("org.chromium.weblayer_private.WebLayerImpl");
// Check version before doing anything else on the implementation side.
if (!(boolean) webLayerClass.getMethod("checkVersion", Integer.TYPE)
.invoke(null, WebLayerVersion.sVersionNumber)) {
throw new UnsupportedVersionException(WebLayerVersion.sVersionNumber);
}
return IWebLayer.Stub.asInterface( private static void checkAvailable(Context context) {
(IBinder) webLayerClass.getMethod("create").invoke(null)); if (!isAvailable(context)) {
} catch (UnsupportedVersionException e) { throw new UnsupportedVersionException(sLoader.getVersion());
throw e;
} catch (Exception e) {
throw new APICallException(e);
} }
} }
/** /**
* Asynchronously creates and initializes WebLayer. Calling this more than once returns the same * Asynchronously creates and initializes WebLayer. Calling this more than once returns the same
* object. Both this method and {@link #loadSync} yield the same instance of {@link WebLayer}. * object. Both this method and {@link #loadSync} yield the same instance of {@link WebLayer}.
* <p>
* {@link callback} is supplied null if unable to load WebLayer. In general, the only time null
* is supplied is if there is an unexpected error.
* *
* @param appContext The hosting application's Context. * @param appContext The hosting application's Context.
* @param callback {@link Callback} which will receive the WebLayer instance. * @param callback {@link Callback} which will receive the WebLayer instance.
* @throws UnsupportedVersionException If {@link #isAvailable} returns false. See
* {@link #isAvailable} for details.
*/ */
public static void loadAsync(@NonNull Context appContext, @NonNull Callback<WebLayer> callback) public static void loadAsync(@NonNull Context appContext, @NonNull Callback<WebLayer> callback)
throws UnsupportedVersionException { throws UnsupportedVersionException {
ThreadCheck.ensureOnUiThread(); ThreadCheck.ensureOnUiThread();
if (sLoader == null) sLoader = new WebLayerLoader(); checkAvailable(appContext);
sLoader.loadAsync(appContext.getApplicationContext(), callback); appContext = appContext.getApplicationContext();
getWebLayerLoader(appContext).loadAsync(appContext, callback);
} }
/** /**
...@@ -102,16 +100,46 @@ public final class WebLayer { ...@@ -102,16 +100,46 @@ public final class WebLayer {
* Both this method and {@link #loadAsync} yield the same instance of {@link WebLayer}. * Both this method and {@link #loadAsync} yield the same instance of {@link WebLayer}.
* It is safe to call this method after {@link #loadAsync} to block until the ongoing load * It is safe to call this method after {@link #loadAsync} to block until the ongoing load
* finishes (or immediately return its result if already finished). * finishes (or immediately return its result if already finished).
* <p>
* This returns null if unable to load WebLayer. In general, the only time null
* is returns is if there is an unexpected error loading.
* *
* @param appContext The hosting application's Context. * @param appContext The hosting application's Context.
* @return a {@link WebLayer} instance. * @return a {@link WebLayer} instance, or null if unable to load WebLayer.
*
* @throws UnsupportedVersionException If {@link #isAvailable} returns false. See
* {@link #isAvailable} for details.
*/ */
@NonNull @Nullable
public static WebLayer loadSync(@NonNull Context appContext) public static WebLayer loadSync(@NonNull Context appContext)
throws UnsupportedVersionException { throws UnsupportedVersionException {
ThreadCheck.ensureOnUiThread(); ThreadCheck.ensureOnUiThread();
if (sLoader == null) sLoader = new WebLayerLoader(); appContext = appContext.getApplicationContext();
return sLoader.loadSync(appContext.getApplicationContext()); checkAvailable(appContext);
return getWebLayerLoader(appContext).loadSync(appContext);
}
private static WebLayerLoader getWebLayerLoader(Context appContext) {
if (sLoader == null) sLoader = new WebLayerLoader(appContext);
return sLoader;
}
/**
* Returns the supported version. Using any functions defined in a newer version than
* returned by {@link getSupportedMajorVersion} result in throwing an
* UnsupportedOperationException.
* <p> For example, consider the function {@link setBottomBar}, and further assume
* {@link setBottomBar} was added in version 11. If {@link getSupportedMajorVersion}
* returns 10, then calling {@link setBottomBar} returns in an UnsupportedOperationException.
* OTOH, if {@link getSupportedMajorVersion} returns 12, then {@link setBottomBar} works as
* expected
*
* @return the supported version, or -1 if WebLayer is not available.
*/
public static int getSupportedMajorVersion(Context context) {
ThreadCheck.ensureOnUiThread();
context = context.getApplicationContext();
return getWebLayerLoader(context).getMajorVersion();
} }
/** /**
...@@ -121,12 +149,59 @@ public final class WebLayer { ...@@ -121,12 +149,59 @@ public final class WebLayer {
@NonNull @NonNull
private final List<Callback<WebLayer>> mCallbacks = new ArrayList<>(); private final List<Callback<WebLayer>> mCallbacks = new ArrayList<>();
@Nullable @Nullable
private IWebLayerFactory mFactory;
@Nullable
private IWebLayer mIWebLayer; private IWebLayer mIWebLayer;
@Nullable @Nullable
private WebLayer mWebLayer; private WebLayer mWebLayer;
// True if WebLayer is available and compatible with this client.
private final boolean mAvailable;
private final int mMajorVersion;
private final String mVersion;
/**
* Creates WebLayerLoader. This does a minimal amount of loading
*/
public WebLayerLoader(@NonNull Context appContext) {
ClassLoader remoteClassLoader;
boolean available = false;
int majorVersion = -1;
String version = "<unavailable>";
try {
remoteClassLoader = getOrCreateRemoteClassLoader(appContext);
Class factoryClass = remoteClassLoader.loadClass(
"org.chromium.weblayer_private.WebLayerFactoryImpl");
mFactory = IWebLayerFactory.Stub.asInterface(
(IBinder) factoryClass
.getMethod("create", String.class, int.class, int.class)
.invoke(null, WebLayerClientVersionConstants.PRODUCT_VERSION,
WebLayerClientVersionConstants.PRODUCT_MAJOR_VERSION,
WebLayerVersion.sVersionNumber));
available = mFactory.isClientSupported();
majorVersion = mFactory.getImplementationMajorVersion();
version = mFactory.getImplementationVersion();
} catch (PackageManager.NameNotFoundException | ReflectiveOperationException
| RemoteException e) {
Log.e("WebLayer", "Unable to create WebLayerFactory", e);
}
mAvailable = available;
mMajorVersion = majorVersion;
mVersion = version;
}
public boolean isAvailable() {
return mAvailable;
}
public int getMajorVersion() {
return mMajorVersion;
}
public String getVersion() {
return mVersion;
}
public void loadAsync(@NonNull Context appContext, @NonNull Callback<WebLayer> callback) public void loadAsync(@NonNull Context appContext, @NonNull Callback<WebLayer> callback) {
throws UnsupportedVersionException {
if (mWebLayer != null) { if (mWebLayer != null) {
callback.onResult(mWebLayer); callback.onResult(mWebLayer);
return; return;
...@@ -135,13 +210,15 @@ public final class WebLayer { ...@@ -135,13 +210,15 @@ public final class WebLayer {
if (mIWebLayer != null) { if (mIWebLayer != null) {
return; // Already loading. return; // Already loading.
} }
if (getIWebLayer(appContext) == null) {
// Unable to create WebLayer. This generally shouldn't happen.
onWebLayerReady();
return;
}
try { try {
getIWebLayer(appContext) getIWebLayer(appContext)
.loadAsync(ObjectWrapper.wrap(appContext), .loadAsync(ObjectWrapper.wrap(appContext),
ObjectWrapper.wrap((ValueCallback<Boolean>) result -> { ObjectWrapper.wrap((ValueCallback<Boolean>) result -> {
// TODO: figure out when |result| is false and what to do in
// such a scenario.
assert result;
onWebLayerReady(); onWebLayerReady();
})); }));
} catch (RemoteException e) { } catch (RemoteException e) {
...@@ -149,11 +226,15 @@ public final class WebLayer { ...@@ -149,11 +226,15 @@ public final class WebLayer {
} }
} }
@NonNull public WebLayer loadSync(@NonNull Context appContext) {
public WebLayer loadSync(@NonNull Context appContext) throws UnsupportedVersionException {
if (mWebLayer != null) { if (mWebLayer != null) {
return mWebLayer; return mWebLayer;
} }
if (getIWebLayer(appContext) == null) {
// Error in creating WebLayer. This generally shouldn't happen.
onWebLayerReady();
return null;
}
try { try {
getIWebLayer(appContext).loadSync(ObjectWrapper.wrap(appContext)); getIWebLayer(appContext).loadSync(ObjectWrapper.wrap(appContext));
onWebLayerReady(); onWebLayerReady();
...@@ -163,11 +244,15 @@ public final class WebLayer { ...@@ -163,11 +244,15 @@ public final class WebLayer {
} }
} }
@NonNull @Nullable
private IWebLayer getIWebLayer(@NonNull Context appContext) private IWebLayer getIWebLayer(@NonNull Context appContext) {
throws UnsupportedVersionException { if (mIWebLayer != null) return mIWebLayer;
if (mIWebLayer == null) { if (!mAvailable) return null;
mIWebLayer = connectToWebLayerImplementation(appContext); try {
mIWebLayer = mFactory.createWebLayer();
} catch (RemoteException e) {
// If |mAvailable| returns true, then create() should always succeed.
throw new AndroidRuntimeException(e);
} }
return mIWebLayer; return mIWebLayer;
} }
...@@ -176,8 +261,7 @@ public final class WebLayer { ...@@ -176,8 +261,7 @@ public final class WebLayer {
if (mWebLayer != null) { if (mWebLayer != null) {
return; return;
} }
assert mIWebLayer != null; if (mIWebLayer != null) mWebLayer = new WebLayer(mIWebLayer);
mWebLayer = new WebLayer(mIWebLayer);
for (Callback<WebLayer> callback : mCallbacks) { for (Callback<WebLayer> callback : mCallbacks) {
callback.onResult(mWebLayer); callback.onResult(mWebLayer);
} }
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer;
final class WebLayerClientVersionConstants {
public static final String PRODUCT_VERSION = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@";
public static final int PRODUCT_MAJOR_VERSION = @MAJOR@;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment