Commit c3aaa23c authored by hanxi's avatar hanxi Committed by Commit bot

Chrome talks to Play to install WebAPKs.

This CL includes:
- Introduce GooglePlayWebApkInstallDelegate which can bind to Phonesky's
  unpublished PlayInstallService to install WebAPKs.
- WebApkInstaller uses the GooglePlayWebApkInstallDelegate to ask Play to
  install WebAPKs. The changes are behind the finch flag.

The internal CL is: https://chrome-internal-review.googlesource.com/#/c/305912/

BUG=662149

Review-Url: https://codereview.chromium.org/2515293004
Cr-Commit-Position: refs/heads/master@{#438001}
parent ee02c5e1
......@@ -60,6 +60,7 @@ import org.chromium.chrome.browser.tabmodel.document.ActivityDelegateImpl;
import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
import org.chromium.chrome.browser.tabmodel.document.StorageDelegate;
import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
import org.chromium.chrome.browser.webapps.GooglePlayWebApkInstallDelegate;
import org.chromium.components.signin.AccountManagerDelegate;
import org.chromium.components.signin.SystemAccountManagerDelegate;
import org.chromium.content.app.ContentApplication;
......@@ -391,8 +392,13 @@ public class ChromeApplication extends ContentApplication {
return null;
}
/** Returns the singleton instance of GooglePlayWebApkInstallDelegate. */
public GooglePlayWebApkInstallDelegate getGooglePlayWebApkInstallDelegate() {
return null;
}
/**
* Returns the Singleton instance of the DocumentTabModelSelector.
* Returns the singleton instance of the DocumentTabModelSelector.
* TODO(dfalcantara): Find a better place for this once we differentiate between activity and
* application-level TabModelSelectors.
* @return The DocumentTabModelSelector for the application.
......
......@@ -10,6 +10,7 @@ import android.content.Intent;
import android.os.StrictMode;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
......@@ -20,12 +21,16 @@ import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.components.variations.VariationsAssociatedData;
import org.chromium.webapk.lib.client.WebApkValidator;
/**
* Contains functionality needed for Chrome to host WebAPKs.
*/
public class ChromeWebApkHost {
/** Flag to enable installing WebAPKs using Google Play. */
private static final String PLAY_INSTALL = "play_install";
private static final String TAG = "ChromeWebApkHost";
private static Boolean sEnabledForTesting;
......@@ -44,15 +49,21 @@ public class ChromeWebApkHost {
return isEnabledInPrefs();
}
/** Return whether installing WebAPKs using Google Play is enabled. */
public static boolean canUseGooglePlayToInstallWebApk() {
if (!isEnabled()) return false;
return TextUtils.equals(VariationsAssociatedData.getVariationParamValue(
ChromeFeatureList.WEBAPKS, PLAY_INSTALL), "true");
}
@CalledByNative
private static boolean areWebApkEnabled() {
return ChromeWebApkHost.isEnabled();
}
/**
* Check the cached value to figure out if the feature is enabled. We have
* to use the cached value because native library may not yet been loaded.
*
* Check the cached value to figure out if the feature is enabled. We have to use the cached
* value because native library may not yet been loaded.
* @return Whether the feature is enabled.
*/
private static boolean isEnabledInPrefs() {
......
// Copyright 2016 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.chrome.browser.webapps;
import android.support.annotation.IntDef;
import org.chromium.base.Callback;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Defines an interface for installing WebAPKs via Google Play.
*/
public interface GooglePlayWebApkInstallDelegate {
/**
* The app state transitions provided by Google Play during download and installation process.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({INVALID, DOWNLOAD_PENDING, DOWNLOADING, DOWNLOAD_CANCELLED, DOWNLOAD_ERROR,
INSTALLING, INSTALL_ERROR, INSTALLED})
public @interface InstallerPackageEvent {}
public static final int INVALID = -1;
public static final int DOWNLOAD_PENDING = 0;
public static final int DOWNLOADING = 1;
public static final int DOWNLOAD_CANCELLED = 2;
public static final int DOWNLOAD_ERROR = 3;
public static final int INSTALLING = 4;
public static final int INSTALL_ERROR = 5;
public static final int INSTALLED = 6;
/**
* Uses Google Play to install WebAPK asynchronously.
* @param packageName The package name of WebAPK to install.
* @param version The version of WebAPK to install.
* @param title The title of the WebAPK to display during installation.
* @param token The token from WebAPK Minter Server.
* @param url The start URL of the WebAPK to install.
* @param callback The callback to invoke when the install is either completed or failed.
* @return True if the install was started. A "true" value does not guarantee that the install
* succeeds.
*/
boolean installAsync(String packageName, int version, String title, String token,
String url, Callback<Boolean> callback);
/**
* Calls the callback once the installation either succeeded or failed.
* @param packageName The package name of WebAPK for the installation.
* @param event The result of the install.
*/
void onGotInstallEvent(String packageName, @InstallerPackageEvent int event);
}
......@@ -13,9 +13,11 @@ import android.os.Looper;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Callback;
import org.chromium.base.ContentUriUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.util.IntentUtils;
......@@ -45,8 +47,13 @@ public class WebApkInstaller {
/** Weak pointer to the native WebApkInstaller. */
private long mNativePointer;
/** Talks to Google Play to install WebAPKs. */
private GooglePlayWebApkInstallDelegate mGooglePlayWebApkInstallDelegate;
private WebApkInstaller(long nativePtr) {
mNativePointer = nativePtr;
ChromeApplication application = (ChromeApplication) ContextUtils.getApplicationContext();
mGooglePlayWebApkInstallDelegate = application.getGooglePlayWebApkInstallDelegate();
}
@CalledByNative
......@@ -54,9 +61,17 @@ public class WebApkInstaller {
return new WebApkInstaller(nativePtr);
}
@CalledByNative
private boolean hasGooglePlayWebApkInstallDelegate() {
return mGooglePlayWebApkInstallDelegate != null;
}
@CalledByNative
private void destroy() {
if (mListener != null) {
ApplicationStatus.unregisterApplicationStateListener(mListener);
}
mListener = null;
mNativePointer = 0;
}
......@@ -86,7 +101,67 @@ public class WebApkInstaller {
}
/**
* Send intent to Android to show prompt and install downloaded WebAPK.
* Installs a WebAPK from Google Play and monitors the installation.
* @param packageName The package name of the WebAPK to install.
* @param version The version of WebAPK to install.
* @param title The title of the WebAPK to display during installation.
* @param token The token from WebAPK Server.
* @param url The start URL of the WebAPK to install.
* @return True if the install was started. A "true" return value does not guarantee that the
* install succeeds.
*/
@CalledByNative
private boolean installWebApkFromGooglePlayAsync(String packageName, int version, String title,
String token, String url) {
if (mGooglePlayWebApkInstallDelegate == null) return false;
Callback<Boolean> callback = new Callback<Boolean>() {
@Override
public void onResult(Boolean success) {
if (mNativePointer != 0) {
nativeOnInstallFinished(mNativePointer, success);
}
}
};
return mGooglePlayWebApkInstallDelegate.installAsync(packageName, version, title, token,
url, callback);
}
/**
* Updates a WebAPK.
* @param filePath File to update.
* @return True if the update was started. A "true" return value does not guarantee that the
* update succeeds.
*/
@CalledByNative
private boolean updateAsyncFromNative(String filePath) {
mIsInstall = false;
return installDownloadedWebApk(filePath);
}
/**
* Updates a WebAPK using Google Play.
* @param packageName The package name of the WebAPK to install.
* @param version The version of WebAPK to install.
* @param title The title of the WebAPK to display during installation.
* @param token The token from WebAPK Server.
* @param url The start URL of the WebAPK to install.
* @return True if the update was started. A "true" return value does not guarantee that the
* update succeeds.
*/
@CalledByNative
private boolean updateAsyncFromGooglePlay(String packageName, int version, String title,
String token, String url) {
if (mGooglePlayWebApkInstallDelegate == null) return false;
// TODO(hanxi):crbug.com/634499. Adds a callback to show an infobar after the update
// succeeded.
return mGooglePlayWebApkInstallDelegate.installAsync(packageName, version, title, token,
url, null);
}
/**
* Sends intent to Android to show prompt and install downloaded WebAPK.
* @param filePath File to install.
*/
private boolean installDownloadedWebApk(String filePath) {
......@@ -132,18 +207,6 @@ public class WebApkInstaller {
}
}
/**
* Updates a WebAPK.
* @param filePath File to update.
* @return True if the update was started. A "true" return value does not guarantee that the
* update succeeds.
*/
@CalledByNative
private boolean updateAsyncFromNative(String filePath) {
mIsInstall = false;
return installDownloadedWebApk(filePath);
}
private ApplicationStatus.ApplicationStateListener createApplicationStateListener() {
return new ApplicationStatus.ApplicationStateListener() {
@Override
......
......@@ -1031,6 +1031,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java",
"java/src/org/chromium/chrome/browser/webapps/FullScreenActivity.java",
"java/src/org/chromium/chrome/browser/webapps/FullScreenDelegateFactory.java",
"java/src/org/chromium/chrome/browser/webapps/GooglePlayWebApkInstallDelegate.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java",
......
......@@ -19,7 +19,10 @@ message WebApkResponse {
// URL to download WebAPK.
optional string signed_download_url = 3;
reserved 4;
// Unique id identifying session with WebAPK server.
optional string token = 6;
reserved 4, 5;
}
// Sent as part of request to create or update a WebAPK.
......
......@@ -13,6 +13,7 @@
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
......@@ -315,6 +316,39 @@ bool WebApkInstaller::StartUpdateUsingDownloadedWebApk(
env, java_ref_, java_file_path);
}
bool WebApkInstaller::HasGooglePlayWebApkInstallDelegate() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_WebApkInstaller_hasGooglePlayWebApkInstallDelegate(
env, java_ref_);
}
bool WebApkInstaller::InstallOrUpdateWebApkFromGooglePlay(
const std::string& package_name,
int version,
const std::string& token) {
webapk_package_ = package_name;
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> java_webapk_package =
base::android::ConvertUTF8ToJavaString(env, webapk_package_);
base::android::ScopedJavaLocalRef<jstring> java_title =
base::android::ConvertUTF16ToJavaString(env, shortcut_info_.user_title);
base::android::ScopedJavaLocalRef<jstring> java_token =
base::android::ConvertUTF8ToJavaString(env, token);
base::android::ScopedJavaLocalRef<jstring> java_url =
base::android::ConvertUTF8ToJavaString(env, shortcut_info_.url.spec());
if (task_type_ == WebApkInstaller::INSTALL) {
return Java_WebApkInstaller_installWebApkFromGooglePlayAsync(
env, java_ref_, java_webapk_package, version, java_title, java_token,
java_url);
} else {
return Java_WebApkInstaller_updateAsyncFromGooglePlay(
env, java_ref_, java_webapk_package, version, java_title, java_token,
java_url);
}
}
void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
timer_.Stop();
......@@ -339,6 +373,17 @@ void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
OnFailure();
return;
}
if (HasGooglePlayWebApkInstallDelegate()) {
int version = 1;
base::StringToInt(response->version(), &version);
if (!InstallOrUpdateWebApkFromGooglePlay(
response->package_name(), version, response->token())) {
OnFailure();
}
return;
}
OnGotWebApkDownloadUrl(signed_download_url, response->package_name());
}
......
......@@ -105,6 +105,18 @@ class WebApkInstaller : public net::URLFetcherDelegate {
JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& java_file_path);
// Returns whether the Google Play install delegate is available.
// Note: it is possible that this delegate is null even when installing
// WebAPKs using Google Play is enabled.
virtual bool HasGooglePlayWebApkInstallDelegate();
// Called when the package name of the WebAPK is available and the install
// or update request is handled by Google Play.
virtual bool InstallOrUpdateWebApkFromGooglePlay(
const std::string& package_name,
int version,
const std::string& token);
// Called when the request to install the WebAPK is sent to Google Play.
void OnSuccess();
......
......@@ -52,12 +52,17 @@ const char* kDownloadedWebApkPackageName = "party.unicode";
// WebApkInstaller subclass where
// WebApkInstaller::StartInstallingDownloadedWebApk() and
// WebApkInstaller::StartUpdateUsingDownloadedWebApk() are stubbed out.
// WebApkInstaller::StartUpdateUsingDownloadedWebApk() and
// WebApkInstaller::HasGooglePlayWebApkInstallDelegate() and
// WebApkInstaller::InstallOrUpdateWebApkFromGooglePlay() are stubbed out.
class TestWebApkInstaller : public WebApkInstaller {
public:
TestWebApkInstaller(const ShortcutInfo& shortcut_info,
const SkBitmap& shortcut_icon)
: WebApkInstaller(shortcut_info, shortcut_icon) {}
const SkBitmap& shortcut_icon,
bool has_google_play_webapk_install_delegate)
: WebApkInstaller(shortcut_info, shortcut_icon),
has_google_play_webapk_install_delegate_(
has_google_play_webapk_install_delegate) {}
bool StartInstallingDownloadedWebApk(
JNIEnv* env,
......@@ -73,6 +78,17 @@ class TestWebApkInstaller : public WebApkInstaller {
return true;
}
bool HasGooglePlayWebApkInstallDelegate() override {
return has_google_play_webapk_install_delegate_;
}
bool InstallOrUpdateWebApkFromGooglePlay(const std::string& package_name,
int version,
const std::string& token) override {
PostTaskToRunSuccessCallback();
return true;
}
void PostTaskToRunSuccessCallback() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
......@@ -80,6 +96,9 @@ class TestWebApkInstaller : public WebApkInstaller {
}
private:
// Whether the Google Play install delegate is available.
bool has_google_play_webapk_install_delegate_;
DISALLOW_COPY_AND_ASSIGN(TestWebApkInstaller);
};
......@@ -89,9 +108,15 @@ class WebApkInstallerRunner {
explicit WebApkInstallerRunner(const GURL& best_icon_url)
: url_request_context_getter_(new net::TestURLRequestContextGetter(
base::ThreadTaskRunnerHandle::Get())),
best_icon_url_(best_icon_url) {}
best_icon_url_(best_icon_url),
has_google_play_webapk_install_delegate_(false) {}
~WebApkInstallerRunner() {}
void SetHasGooglePlayWebApkInstallDelegate(bool has_delegate) {
has_google_play_webapk_install_delegate_ = has_delegate;
}
void RunInstallWebApk() {
WebApkInstaller* installer = CreateWebApkInstaller();
......@@ -123,7 +148,8 @@ class WebApkInstallerRunner {
info.best_icon_url = best_icon_url_;
// WebApkInstaller owns itself.
WebApkInstaller* installer = new TestWebApkInstaller(info, SkBitmap());
WebApkInstaller* installer = new TestWebApkInstaller(
info, SkBitmap(), has_google_play_webapk_install_delegate_);
installer->SetTimeoutMs(100);
return installer;
}
......@@ -154,6 +180,9 @@ class WebApkInstallerRunner {
// Whether the installation process succeeded.
bool success_;
// Whether the Google Play install delegate is available.
bool has_google_play_webapk_install_delegate_;
DISALLOW_COPY_AND_ASSIGN(WebApkInstallerRunner);
};
......@@ -331,3 +360,11 @@ TEST_F(WebApkInstallerTest, UpdateSuccess) {
runner->RunUpdateWebApk();
EXPECT_TRUE(runner->success());
}
// Test installation succeeds using Google Play.
TEST_F(WebApkInstallerTest, InstallFromGooglePlaySuccess) {
std::unique_ptr<WebApkInstallerRunner> runner = CreateWebApkInstallerRunner();
runner->SetHasGooglePlayWebApkInstallDelegate(true);
runner->RunInstallWebApk();
EXPECT_TRUE(runner->success());
}
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