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; ...@@ -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.DocumentTabModelSelector;
import org.chromium.chrome.browser.tabmodel.document.StorageDelegate; import org.chromium.chrome.browser.tabmodel.document.StorageDelegate;
import org.chromium.chrome.browser.tabmodel.document.TabDelegate; 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.AccountManagerDelegate;
import org.chromium.components.signin.SystemAccountManagerDelegate; import org.chromium.components.signin.SystemAccountManagerDelegate;
import org.chromium.content.app.ContentApplication; import org.chromium.content.app.ContentApplication;
...@@ -391,8 +392,13 @@ public class ChromeApplication extends ContentApplication { ...@@ -391,8 +392,13 @@ public class ChromeApplication extends ContentApplication {
return null; 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 * TODO(dfalcantara): Find a better place for this once we differentiate between activity and
* application-level TabModelSelectors. * application-level TabModelSelectors.
* @return The DocumentTabModelSelector for the application. * @return The DocumentTabModelSelector for the application.
......
...@@ -10,6 +10,7 @@ import android.content.Intent; ...@@ -10,6 +10,7 @@ import android.content.Intent;
import android.os.StrictMode; import android.os.StrictMode;
import android.provider.Settings; import android.provider.Settings;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import org.chromium.base.CommandLine; import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
...@@ -20,12 +21,16 @@ import org.chromium.chrome.browser.ChromeFeatureList; ...@@ -20,12 +21,16 @@ import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeVersionInfo; import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager; import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.components.variations.VariationsAssociatedData;
import org.chromium.webapk.lib.client.WebApkValidator; import org.chromium.webapk.lib.client.WebApkValidator;
/** /**
* Contains functionality needed for Chrome to host WebAPKs. * Contains functionality needed for Chrome to host WebAPKs.
*/ */
public class ChromeWebApkHost { 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 final String TAG = "ChromeWebApkHost";
private static Boolean sEnabledForTesting; private static Boolean sEnabledForTesting;
...@@ -44,15 +49,21 @@ public class ChromeWebApkHost { ...@@ -44,15 +49,21 @@ public class ChromeWebApkHost {
return isEnabledInPrefs(); 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 @CalledByNative
private static boolean areWebApkEnabled() { private static boolean areWebApkEnabled() {
return ChromeWebApkHost.isEnabled(); return ChromeWebApkHost.isEnabled();
} }
/** /**
* Check the cached value to figure out if the feature is enabled. We have * Check the cached value to figure out if the feature is enabled. We have to use the cached
* to use the cached value because native library may not yet been loaded. * value because native library may not yet been loaded.
*
* @return Whether the feature is enabled. * @return Whether the feature is enabled.
*/ */
private static boolean isEnabledInPrefs() { 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; ...@@ -13,9 +13,11 @@ import android.os.Looper;
import org.chromium.base.ApplicationState; import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.Callback;
import org.chromium.base.ContentUriUtils; import org.chromium.base.ContentUriUtils;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.ShortcutHelper; import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.banners.InstallerDelegate; import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.util.IntentUtils; import org.chromium.chrome.browser.util.IntentUtils;
...@@ -45,8 +47,13 @@ public class WebApkInstaller { ...@@ -45,8 +47,13 @@ public class WebApkInstaller {
/** Weak pointer to the native WebApkInstaller. */ /** Weak pointer to the native WebApkInstaller. */
private long mNativePointer; private long mNativePointer;
/** Talks to Google Play to install WebAPKs. */
private GooglePlayWebApkInstallDelegate mGooglePlayWebApkInstallDelegate;
private WebApkInstaller(long nativePtr) { private WebApkInstaller(long nativePtr) {
mNativePointer = nativePtr; mNativePointer = nativePtr;
ChromeApplication application = (ChromeApplication) ContextUtils.getApplicationContext();
mGooglePlayWebApkInstallDelegate = application.getGooglePlayWebApkInstallDelegate();
} }
@CalledByNative @CalledByNative
...@@ -54,9 +61,17 @@ public class WebApkInstaller { ...@@ -54,9 +61,17 @@ public class WebApkInstaller {
return new WebApkInstaller(nativePtr); return new WebApkInstaller(nativePtr);
} }
@CalledByNative
private boolean hasGooglePlayWebApkInstallDelegate() {
return mGooglePlayWebApkInstallDelegate != null;
}
@CalledByNative @CalledByNative
private void destroy() { private void destroy() {
ApplicationStatus.unregisterApplicationStateListener(mListener); if (mListener != null) {
ApplicationStatus.unregisterApplicationStateListener(mListener);
}
mListener = null;
mNativePointer = 0; mNativePointer = 0;
} }
...@@ -86,7 +101,67 @@ public class WebApkInstaller { ...@@ -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. * @param filePath File to install.
*/ */
private boolean installDownloadedWebApk(String filePath) { private boolean installDownloadedWebApk(String filePath) {
...@@ -132,18 +207,6 @@ public class WebApkInstaller { ...@@ -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() { private ApplicationStatus.ApplicationStateListener createApplicationStateListener() {
return new ApplicationStatus.ApplicationStateListener() { return new ApplicationStatus.ApplicationStateListener() {
@Override @Override
......
...@@ -1031,6 +1031,7 @@ chrome_java_sources = [ ...@@ -1031,6 +1031,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java", "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/FullScreenActivity.java",
"java/src/org/chromium/chrome/browser/webapps/FullScreenDelegateFactory.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/WebApkActivity.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java", "java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java", "java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java",
......
...@@ -19,7 +19,10 @@ message WebApkResponse { ...@@ -19,7 +19,10 @@ message WebApkResponse {
// URL to download WebAPK. // URL to download WebAPK.
optional string signed_download_url = 3; 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. // Sent as part of request to create or update a WebAPK.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -315,6 +316,39 @@ bool WebApkInstaller::StartUpdateUsingDownloadedWebApk( ...@@ -315,6 +316,39 @@ bool WebApkInstaller::StartUpdateUsingDownloadedWebApk(
env, java_ref_, java_file_path); 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) { void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
timer_.Stop(); timer_.Stop();
...@@ -339,6 +373,17 @@ void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) { ...@@ -339,6 +373,17 @@ void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
OnFailure(); OnFailure();
return; 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()); OnGotWebApkDownloadUrl(signed_download_url, response->package_name());
} }
......
...@@ -105,6 +105,18 @@ class WebApkInstaller : public net::URLFetcherDelegate { ...@@ -105,6 +105,18 @@ class WebApkInstaller : public net::URLFetcherDelegate {
JNIEnv* env, JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& java_file_path); 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. // Called when the request to install the WebAPK is sent to Google Play.
void OnSuccess(); void OnSuccess();
......
...@@ -52,12 +52,17 @@ const char* kDownloadedWebApkPackageName = "party.unicode"; ...@@ -52,12 +52,17 @@ const char* kDownloadedWebApkPackageName = "party.unicode";
// WebApkInstaller subclass where // WebApkInstaller subclass where
// WebApkInstaller::StartInstallingDownloadedWebApk() and // WebApkInstaller::StartInstallingDownloadedWebApk() and
// WebApkInstaller::StartUpdateUsingDownloadedWebApk() are stubbed out. // WebApkInstaller::StartUpdateUsingDownloadedWebApk() and
// WebApkInstaller::HasGooglePlayWebApkInstallDelegate() and
// WebApkInstaller::InstallOrUpdateWebApkFromGooglePlay() are stubbed out.
class TestWebApkInstaller : public WebApkInstaller { class TestWebApkInstaller : public WebApkInstaller {
public: public:
TestWebApkInstaller(const ShortcutInfo& shortcut_info, TestWebApkInstaller(const ShortcutInfo& shortcut_info,
const SkBitmap& shortcut_icon) const SkBitmap& shortcut_icon,
: WebApkInstaller(shortcut_info, 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( bool StartInstallingDownloadedWebApk(
JNIEnv* env, JNIEnv* env,
...@@ -73,6 +78,17 @@ class TestWebApkInstaller : public WebApkInstaller { ...@@ -73,6 +78,17 @@ class TestWebApkInstaller : public WebApkInstaller {
return true; 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() { void PostTaskToRunSuccessCallback() {
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, FROM_HERE,
...@@ -80,6 +96,9 @@ class TestWebApkInstaller : public WebApkInstaller { ...@@ -80,6 +96,9 @@ class TestWebApkInstaller : public WebApkInstaller {
} }
private: private:
// Whether the Google Play install delegate is available.
bool has_google_play_webapk_install_delegate_;
DISALLOW_COPY_AND_ASSIGN(TestWebApkInstaller); DISALLOW_COPY_AND_ASSIGN(TestWebApkInstaller);
}; };
...@@ -89,9 +108,15 @@ class WebApkInstallerRunner { ...@@ -89,9 +108,15 @@ class WebApkInstallerRunner {
explicit WebApkInstallerRunner(const GURL& best_icon_url) explicit WebApkInstallerRunner(const GURL& best_icon_url)
: url_request_context_getter_(new net::TestURLRequestContextGetter( : url_request_context_getter_(new net::TestURLRequestContextGetter(
base::ThreadTaskRunnerHandle::Get())), base::ThreadTaskRunnerHandle::Get())),
best_icon_url_(best_icon_url) {} best_icon_url_(best_icon_url),
has_google_play_webapk_install_delegate_(false) {}
~WebApkInstallerRunner() {} ~WebApkInstallerRunner() {}
void SetHasGooglePlayWebApkInstallDelegate(bool has_delegate) {
has_google_play_webapk_install_delegate_ = has_delegate;
}
void RunInstallWebApk() { void RunInstallWebApk() {
WebApkInstaller* installer = CreateWebApkInstaller(); WebApkInstaller* installer = CreateWebApkInstaller();
...@@ -123,7 +148,8 @@ class WebApkInstallerRunner { ...@@ -123,7 +148,8 @@ class WebApkInstallerRunner {
info.best_icon_url = best_icon_url_; info.best_icon_url = best_icon_url_;
// WebApkInstaller owns itself. // WebApkInstaller owns itself.
WebApkInstaller* installer = new TestWebApkInstaller(info, SkBitmap()); WebApkInstaller* installer = new TestWebApkInstaller(
info, SkBitmap(), has_google_play_webapk_install_delegate_);
installer->SetTimeoutMs(100); installer->SetTimeoutMs(100);
return installer; return installer;
} }
...@@ -154,6 +180,9 @@ class WebApkInstallerRunner { ...@@ -154,6 +180,9 @@ class WebApkInstallerRunner {
// Whether the installation process succeeded. // Whether the installation process succeeded.
bool success_; bool success_;
// Whether the Google Play install delegate is available.
bool has_google_play_webapk_install_delegate_;
DISALLOW_COPY_AND_ASSIGN(WebApkInstallerRunner); DISALLOW_COPY_AND_ASSIGN(WebApkInstallerRunner);
}; };
...@@ -331,3 +360,11 @@ TEST_F(WebApkInstallerTest, UpdateSuccess) { ...@@ -331,3 +360,11 @@ TEST_F(WebApkInstallerTest, UpdateSuccess) {
runner->RunUpdateWebApk(); runner->RunUpdateWebApk();
EXPECT_TRUE(runner->success()); 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