Commit 9f38f74b authored by hanxi's avatar hanxi Committed by Commit bot

Make AppBannerInfoBar install WebAPK.

On UI, the text for installing a WebAPK in the App Banner is:
1) "Add to Homescreen" -> "Adding" -> "Open" for a sucessful
   installation
2) "Add to Homescreen" -> "Adding" -> "Unable to save" toast
   for an unsucessful installation.

WebAPK-specific logic is added to AppBannerInfobarDelegateAndroid
to show the above text. Besides, checking whether a site is WebAPK
Compattive is based on 1) installable check + 2) url is over
 "https" + 3) WebAPK is enabled on command line.

Currently, WebAPK is installed as an untrusted source with Android
installation dialog pops up. User could choose cancel the installation
even after the WebAPK has been downloaded locally. To detect the
cancel behavior, AppBannerInfobarDelegateAndroid checks whether the
WebAPK has been installed or not after Chrome is resumed. This isn't an
ideal way, but it seems we can't find a better way to detect the cancel
event. This logic will be removed when WebAPK is installed by Play soon.

This CL disables creating WebAPKs by selecting add-to-homescreen from
the app menu. Instead, in a follow up CL, selecting from the app menu
will make the app banner info bar shown to install WebAPKs.

BUG=627250

Review-Url: https://codereview.chromium.org/2259553002
Cr-Commit-Position: refs/heads/master@{#415365}
parent eaf49626
......@@ -42,6 +42,9 @@ public class AppBannerInfoBarAndroid extends ConfirmInfoBar implements View.OnCl
// Data for web app installs.
private final String mAppUrl;
// Indicates whether the infobar is for installing a WebAPK.
private boolean mIsWebApk;
// Banner for native apps.
private AppBannerInfoBarAndroid(String appTitle, Bitmap iconBitmap, AppData data) {
super(0, iconBitmap, appTitle, null, data.installButtonText(), null);
......@@ -52,11 +55,13 @@ public class AppBannerInfoBarAndroid extends ConfirmInfoBar implements View.OnCl
}
// Banner for web apps.
private AppBannerInfoBarAndroid(String appTitle, Bitmap iconBitmap, String url) {
private AppBannerInfoBarAndroid(String appTitle, Bitmap iconBitmap, String url,
boolean isWebApk) {
super(0, iconBitmap, appTitle, null, getAddToHomescreenText(), null);
mAppTitle = appTitle;
mAppData = null;
mAppUrl = url;
mIsWebApk = isWebApk;
mInstallState = INSTALL_STATE_NOT_INSTALLED;
}
......@@ -76,8 +81,7 @@ public class AppBannerInfoBarAndroid extends ConfirmInfoBar implements View.OnCl
if (mAppData != null) {
// Native app.
layout.getPrimaryButton().setButtonColor(ApiCompatibilityUtils.getColor(
getContext().getResources(),
R.color.app_banner_install_button_bg));
context.getResources(), R.color.app_banner_install_button_bg));
mMessageLayout.addRatingBar(mAppData.rating());
mMessageLayout.setContentDescription(context.getString(
R.string.app_banner_view_native_app_accessibility, mAppTitle,
......@@ -136,20 +140,27 @@ public class AppBannerInfoBarAndroid extends ConfirmInfoBar implements View.OnCl
}
private void updateButton() {
if (mButton == null || mAppData == null) return;
if (mButton == null || (mAppData == null && !mIsWebApk)) return;
String text;
String accessibilityText = null;
boolean enabled = true;
Context context = getContext();
if (mInstallState == INSTALL_STATE_NOT_INSTALLED) {
if (mIsWebApk) {
// If the installation of the WebAPK fails, the banner will disappear and
// a failure toast will be shown.
return;
}
text = mAppData.installButtonText();
accessibilityText = getContext().getString(
accessibilityText = context.getString(
R.string.app_banner_view_native_app_install_accessibility, text);
} else if (mInstallState == INSTALL_STATE_INSTALLING) {
text = getContext().getString(R.string.app_banner_installing);
text = mIsWebApk ? context.getString(R.string.app_banner_installing_webapk)
: context.getString(R.string.app_banner_installing);
enabled = false;
} else {
text = getContext().getString(R.string.app_banner_open);
text = context.getString(R.string.app_banner_open);
}
mButton.setText(text);
......@@ -173,7 +184,8 @@ public class AppBannerInfoBarAndroid extends ConfirmInfoBar implements View.OnCl
}
@CalledByNative
private static InfoBar createWebAppInfoBar(String appTitle, Bitmap iconBitmap, String url) {
return new AppBannerInfoBarAndroid(appTitle, iconBitmap, url);
private static InfoBar createWebAppInfoBar(String appTitle, Bitmap iconBitmap, String url,
boolean isWebApk) {
return new AppBannerInfoBarAndroid(appTitle, iconBitmap, url, isWebApk);
}
}
......@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.infobar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
......@@ -13,13 +14,17 @@ import android.os.Looper;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.banners.AppData;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.widget.Toast;
/**
* Handles the promotion and installation of an app specified by the current web page. This Java
......@@ -27,6 +32,8 @@ import org.chromium.ui.base.WindowAndroid;
*/
@JNINamespace("banners")
public class AppBannerInfoBarDelegateAndroid {
private static final String TAG = "cr_AppBannerInfoBar";
/** PackageManager to use in place of the real one. */
private static PackageManager sPackageManagerForTests;
......@@ -39,6 +46,15 @@ public class AppBannerInfoBarDelegateAndroid {
/** Monitors for application state changes. */
private final ApplicationStatus.ApplicationStateListener mListener;
/**
* Indicates whether a request to install a WebAPK has started. This flag is set while the
* WebAPK is being installed.
*/
private boolean mIsInstallingWebApk;
/** The package name of the WebAPK. */
private String mWebApkPackage;
/** Overrides the PackageManager for testing. */
@VisibleForTesting
public static void setPackageManagerForTesting(PackageManager manager) {
......@@ -79,9 +95,7 @@ public class AppBannerInfoBarDelegateAndroid {
if (InstallerDelegate.isInstalled(packageManager, packageName)) {
// Open the app.
Intent launchIntent = packageManager.getLaunchIntentForPackage(packageName);
if (launchIntent == null) return true;
context.startActivity(launchIntent);
openApp(context, packageName);
return true;
} else {
// Try installing the app. If the installation was kicked off, return false to prevent
......@@ -96,6 +110,18 @@ public class AppBannerInfoBarDelegateAndroid {
}
}
void openApp(Context context, String packageName) {
Intent launchIntent = getPackageManager(context).getLaunchIntentForPackage(packageName);
if (launchIntent != null) {
try {
context.startActivity(launchIntent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open app : %s!", packageName, e);
return;
}
}
}
private WindowAndroid.IntentCallback createIntentCallback(final AppData appData) {
return new WindowAndroid.IntentCallback() {
@Override
......@@ -128,6 +154,17 @@ public class AppBannerInfoBarDelegateAndroid {
};
}
@CalledByNative
private void openWebApk(String packageName) {
Context context = ContextUtils.getApplicationContext();
PackageManager packageManager = getPackageManager(context);
if (InstallerDelegate.isInstalled(packageManager, packageName)) {
mWebApkPackage = null;
openApp(context, packageName);
}
}
@CalledByNative
private void showAppDetails(Tab tab, AppData appData) {
tab.getWindowAndroid().showIntent(appData.detailsIntent(), null, null);
......@@ -135,12 +172,40 @@ public class AppBannerInfoBarDelegateAndroid {
@CalledByNative
private int determineInstallState(AppData data) {
if (mInstallTask != null) return AppBannerInfoBarAndroid.INSTALL_STATE_INSTALLING;
if (mInstallTask != null || mIsInstallingWebApk) {
return AppBannerInfoBarAndroid.INSTALL_STATE_INSTALLING;
}
PackageManager pm = getPackageManager(ContextUtils.getApplicationContext());
boolean isInstalled = InstallerDelegate.isInstalled(pm, data.packageName());
String packageName = (data != null) ? data.packageName() : mWebApkPackage;
boolean isInstalled = InstallerDelegate.isInstalled(pm, packageName);
return isInstalled ? AppBannerInfoBarAndroid.INSTALL_STATE_INSTALLED
: AppBannerInfoBarAndroid.INSTALL_STATE_NOT_INSTALLED;
: AppBannerInfoBarAndroid.INSTALL_STATE_NOT_INSTALLED;
}
@CalledByNative
/** Set the flag of whether the installation process has been started for the WebAPK. */
private void setWebApkInstallingState(boolean isInstalling) {
mIsInstallingWebApk = isInstalling;
}
@CalledByNative
/** Sets the WebAPK package name. */
private void setWebApkPackageName(String webApkPackage) {
mWebApkPackage = webApkPackage;
}
@CalledByNative
private static void showWebApkInstallFailureToast() {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
Context applicationContext = ContextUtils.getApplicationContext();
Toast toast = Toast.makeText(applicationContext, R.string.fail_to_install_webapk,
Toast.LENGTH_SHORT);
toast.show();
}
});
}
private PackageManager getPackageManager(Context context) {
......
......@@ -5,27 +5,58 @@
package org.chromium.chrome.browser.webapps;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Looper;
import android.provider.Settings;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import java.io.File;
/**
* Java counterpart to webapk_installer.h
* Contains functionality to install / update WebAPKs.
* This Java object is created by and owned by the native WebApkInstaller.
*/
public class WebApkInstaller {
private static final String TAG = "WebApkInstaller";
/** The WebAPK's package name. */
private String mWebApkPackageName;
/** Monitors for application state changes. */
private ApplicationStatus.ApplicationStateListener mListener;
/** Monitors installation progress. */
private InstallerDelegate mInstallTask;
/** Weak pointer to the native WebApkInstaller. */
private long mNativePointer;
private WebApkInstaller(long nativePtr) {
mNativePointer = nativePtr;
}
@CalledByNative
private static WebApkInstaller create(long nativePtr) {
return new WebApkInstaller(nativePtr);
}
@CalledByNative
private void destroy() {
ApplicationStatus.unregisterApplicationStateListener(mListener);
mNativePointer = 0;
}
/**
* Installs a WebAPK.
* @param filePath File to install.
......@@ -34,18 +65,28 @@ public class WebApkInstaller {
* install succeeds.
*/
@CalledByNative
static boolean installAsyncFromNative(String filePath, String packageName) {
private boolean installAsyncFromNative(String filePath, String packageName) {
if (!installingFromUnknownSourcesAllowed()) {
Log.e(TAG,
"WebAPK install failed because installation from unknown sources is disabled.");
return false;
}
mWebApkPackageName = packageName;
// Start monitoring the installation.
PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
mInstallTask = new InstallerDelegate(Looper.getMainLooper(), packageManager,
createInstallerDelegateObserver(), packageName);
mInstallTask.start();
// Start monitoring the application state changes.
mListener = createApplicationStateListener();
ApplicationStatus.registerApplicationStateListener(mListener);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri fileUri = Uri.fromFile(new File(filePath));
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
listenForPackageInstallation(packageName);
ContextUtils.getApplicationContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
return false;
......@@ -53,34 +94,27 @@ public class WebApkInstaller {
return true;
}
private static class WebApkInstallObserver extends BroadcastReceiver {
private final String mPackageName;
public WebApkInstallObserver(String packageName) {
mPackageName = packageName;
}
private static String getPackageName(Intent intent) {
Uri uri = intent.getData();
String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
return pkg;
}
@Override
public void onReceive(Context context, Intent intent) {
if (mPackageName.equals(getPackageName(intent))) {
ShortcutHelper.addWebApkShortcut(context, mPackageName);
context.unregisterReceiver(this);
private InstallerDelegate.Observer createInstallerDelegateObserver() {
return new InstallerDelegate.Observer() {
@Override
public void onInstallFinished(InstallerDelegate task, boolean success) {
if (mInstallTask != task) return;
onInstallFinishedInternal(success);
}
}
};
}
private static void listenForPackageInstallation(String packageName) {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
iFilter.addDataScheme("package");
ContextUtils.getApplicationContext().registerReceiver(
new WebApkInstallObserver(packageName), iFilter);
private void onInstallFinishedInternal(boolean success) {
ApplicationStatus.unregisterApplicationStateListener(mListener);
mInstallTask = null;
if (mNativePointer != 0) {
nativeOnInstallFinished(mNativePointer, success);
}
if (success) {
ShortcutHelper.addWebApkShortcut(ContextUtils.getApplicationContext(),
mWebApkPackageName);
}
}
/**
* Updates a WebAPK.
* @param filePath File to update.
......@@ -89,7 +123,9 @@ public class WebApkInstaller {
* update succeeds.
*/
@CalledByNative
private static boolean updateAsyncFromNative(String filePath, String packageName) {
private boolean updateAsyncFromNative(String filePath, String packageName) {
// TODO(hanxi): Calls back to C++ when the updating is complete or return a failure status
// if fails.
return false;
}
......@@ -107,4 +143,32 @@ public class WebApkInstaller {
return false;
}
}
private ApplicationStatus.ApplicationStateListener createApplicationStateListener() {
return new ApplicationStatus.ApplicationStateListener() {
@Override
public void onApplicationStateChange(int newState) {
if (!ApplicationStatus.hasVisibleActivities()) return;
/**
* Currently WebAPKs aren't installed by Play. A user can cancel the installation
* when the Android native installation dialog shows. The only way to detect the
* user cancelling the installation is to check whether the WebAPK is installed
* when Chrome is resumed. The monitoring of application state changes will be
* removed once WebAPKs are installed by Play.
*/
if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES
&& !isWebApkInstalled(mWebApkPackageName)) {
onInstallFinishedInternal(false);
return;
}
}
};
}
private boolean isWebApkInstalled(String packageName) {
PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
return InstallerDelegate.isInstalled(packageManager, packageName);
}
private native void nativeOnInstallFinished(long nativeWebApkInstaller, boolean success);
}
......@@ -1526,6 +1526,9 @@ To obtain new licenses, connect to the internet and play your downloaded content
<message name="IDS_APP_BANNER_OPEN" desc="Text that indicates that clicking on the button will launch an application. [CHAR-LIMIT=25]">
Open
</message>
<message name="IDS_APP_BANNER_INSTALLING_WEBAPK" desc="Button text indicating that a WebAPK is being installed. [CHAR-LIMIT=25]">
Adding…
</message>
<!-- App banner accessibility strings, used for touch exploration -->
<message name="IDS_APP_BANNER_VIEW_NATIVE_APP_ACCESSIBILITY" desc="Accessibililty text: Describes a prompt promoting the installation of an app related to the current page, including the app's name and its rating.">
......@@ -1588,6 +1591,9 @@ To obtain new licenses, connect to the internet and play your downloaded content
<message name="IDS_ADDED_TO_HOMESCREEN" desc="Indicates that the website with the specified name was added to the user's Home screen.">
<ph name="NAME">%1$s<ex>Google</ex></ph> was added to your Home screen
</message>
<message name="IDS_FAIL_TO_INSTALL_WEBAPK" desc="The installation of a WebAPK fails. [CHAR-LIMIT=27]">
Unable to add
</message>
<!-- WebsiteSettingsPopup (PageInfo dialog) -->
<message name="IDS_PAGE_INFO_SITE_SETTINGS_BUTTON" desc="Text in the button that opens a website's Site Settings from the Page Info dialog.">
......
......@@ -14,6 +14,7 @@
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/shortcut_info.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/webapk/webapk_installer.h"
#include "chrome/browser/banners/app_banner_manager.h"
#include "chrome/browser/banners/app_banner_metrics.h"
#include "chrome/browser/banners/app_banner_settings_helper.h"
......@@ -45,7 +46,8 @@ AppBannerInfoBarDelegateAndroid::AppBannerInfoBarDelegateAndroid(
const content::Manifest& manifest,
const GURL& icon_url,
std::unique_ptr<SkBitmap> icon,
int event_request_id)
int event_request_id,
bool is_webapk)
: weak_manager_(weak_manager),
app_title_(app_title),
manifest_url_(manifest_url),
......@@ -53,7 +55,9 @@ AppBannerInfoBarDelegateAndroid::AppBannerInfoBarDelegateAndroid(
icon_url_(icon_url),
icon_(std::move(icon)),
event_request_id_(event_request_id),
has_user_interaction_(false) {
has_user_interaction_(false),
is_webapk_(is_webapk),
weak_ptr_factory_(this) {
DCHECK(!manifest.IsEmpty());
CreateJavaDelegate();
}
......@@ -71,12 +75,15 @@ AppBannerInfoBarDelegateAndroid::AppBannerInfoBarDelegateAndroid(
native_app_package_(native_app_package),
referrer_(referrer),
event_request_id_(event_request_id),
has_user_interaction_(false) {
has_user_interaction_(false),
weak_ptr_factory_(this) {
DCHECK(!native_app_data_.is_null());
CreateJavaDelegate();
}
AppBannerInfoBarDelegateAndroid::~AppBannerInfoBarDelegateAndroid() {
weak_ptr_factory_.InvalidateWeakPtrs();
if (!has_user_interaction_) {
if (!native_app_data_.is_null())
TrackUserResponse(USER_RESPONSE_NATIVE_APP_IGNORED);
......@@ -93,7 +100,7 @@ AppBannerInfoBarDelegateAndroid::~AppBannerInfoBarDelegateAndroid() {
void AppBannerInfoBarDelegateAndroid::UpdateInstallState(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
if (native_app_data_.is_null())
if (native_app_data_.is_null() && !is_webapk_)
return;
int newState = Java_AppBannerInfoBarDelegateAndroid_determineInstallState(
......@@ -217,56 +224,140 @@ bool AppBannerInfoBarDelegateAndroid::Accept() {
}
if (!native_app_data_.is_null()) {
TrackUserResponse(USER_RESPONSE_NATIVE_APP_ACCEPTED);
JNIEnv* env = base::android::AttachCurrentThread();
TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
if (tab == nullptr) {
TrackDismissEvent(DISMISS_EVENT_ERROR);
return true;
}
ScopedJavaLocalRef<jstring> jreferrer(
ConvertUTF8ToJavaString(env, referrer_));
bool was_opened =
Java_AppBannerInfoBarDelegateAndroid_installOrOpenNativeApp(
env, java_delegate_, tab->GetJavaObject(), native_app_data_,
jreferrer);
if (was_opened) {
TrackDismissEvent(DISMISS_EVENT_APP_OPEN);
} else {
TrackInstallEvent(INSTALL_EVENT_NATIVE_APP_INSTALL_TRIGGERED);
}
SendBannerAccepted(web_contents, "play");
return was_opened;
} else if (!manifest_.IsEmpty()) {
return AcceptNativeApp(web_contents);
} else if (is_webapk_) {
return AcceptWebApk(web_contents);
}
return AcceptWebApp(web_contents);
}
bool AppBannerInfoBarDelegateAndroid::AcceptNativeApp(
content::WebContents* web_contents) {
TrackUserResponse(USER_RESPONSE_NATIVE_APP_ACCEPTED);
JNIEnv* env = base::android::AttachCurrentThread();
TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
if (tab == nullptr) {
TrackDismissEvent(DISMISS_EVENT_ERROR);
return true;
}
ScopedJavaLocalRef<jstring> jreferrer(
ConvertUTF8ToJavaString(env, referrer_));
bool was_opened =
Java_AppBannerInfoBarDelegateAndroid_installOrOpenNativeApp(
env, java_delegate_, tab->GetJavaObject(),
native_app_data_, jreferrer);
if (was_opened) {
TrackDismissEvent(DISMISS_EVENT_APP_OPEN);
} else {
TrackInstallEvent(INSTALL_EVENT_NATIVE_APP_INSTALL_TRIGGERED);
}
SendBannerAccepted(web_contents, "play");
return was_opened;
}
bool AppBannerInfoBarDelegateAndroid::AcceptWebApp(
content::WebContents* web_contents) {
if (manifest_.IsEmpty())
return true;
TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED);
AppBannerSettingsHelper::RecordBannerInstallEvent(
web_contents, manifest_.start_url.spec(),
AppBannerSettingsHelper::WEB);
if (weak_manager_) {
ShortcutInfo info(GURL::EmptyGURL());
info.UpdateFromManifest(manifest_);
info.manifest_url = manifest_url_;
info.icon_url = icon_url_;
info.UpdateSource(ShortcutInfo::SOURCE_APP_BANNER);
const std::string& uid = base::GenerateGUID();
ShortcutHelper::AddToLauncherWithSkBitmap(
web_contents->GetBrowserContext(), info, uid, *icon_.get(),
weak_manager_->FetchWebappSplashScreenImageCallback(uid));
}
SendBannerAccepted(web_contents, "web");
return true;
}
bool AppBannerInfoBarDelegateAndroid::AcceptWebApk(
content::WebContents* web_contents) {
if (manifest_.IsEmpty())
return true;
JNIEnv* env = base::android::AttachCurrentThread();
// |webapk_package_name_| is set when the WebAPK has finished installing.
// If the |webapk_package_name_| is empty, it means the "Add to Homescreen"
// button is pressed, so request WebAPK installation. Otherwise, it means
// the "Open" button is pressed, then open the installed WebAPK.
if (webapk_package_name_.empty()) {
// Request install the WebAPK.
TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED);
AppBannerSettingsHelper::RecordBannerInstallEvent(
web_contents, manifest_.start_url.spec(),
AppBannerSettingsHelper::WEB);
if (weak_manager_) {
ShortcutInfo info(GURL::EmptyGURL());
info.UpdateFromManifest(manifest_);
info.manifest_url = manifest_url_;
info.icon_url = icon_url_;
info.UpdateSource(ShortcutInfo::SOURCE_APP_BANNER);
ShortcutInfo info(GURL::EmptyGURL());
info.UpdateFromManifest(manifest_);
info.manifest_url = manifest_url_;
info.icon_url = icon_url_;
info.UpdateSource(ShortcutInfo::SOURCE_APP_BANNER);
const std::string& uid = base::GenerateGUID();
ShortcutHelper::AddToLauncherWithSkBitmap(
web_contents->GetBrowserContext(), info, uid, *icon_.get(),
weak_manager_->FetchWebappSplashScreenImageCallback(uid));
}
Java_AppBannerInfoBarDelegateAndroid_setWebApkInstallingState(
env, java_delegate_, true);
UpdateInstallState(env, nullptr);
WebApkInstaller::FinishCallback callback = base::Bind(
&AppBannerInfoBarDelegateAndroid::OnWebApkInstallFinished,
weak_ptr_factory_.GetWeakPtr());
DVLOG(1) << "Trigger the installation of the WebAPK.";
ShortcutHelper::InstallWebApkWithSkBitmap(
web_contents->GetBrowserContext(), info, *icon_.get(), callback);
SendBannerAccepted(web_contents, "web");
return true;
// Returns false to prevent the infobar from disappearing.
return false;
}
// Open the WebAPK.
ScopedJavaLocalRef<jstring> java_webapk_package_name =
base::android::ConvertUTF8ToJavaString(env, webapk_package_name_);
Java_AppBannerInfoBarDelegateAndroid_openWebApk(
env, java_delegate_, java_webapk_package_name);
SendBannerAccepted(web_contents, "web");
return true;
}
void AppBannerInfoBarDelegateAndroid::OnWebApkInstallFinished(
bool success,
const std::string& webapk_package_name) {
JNIEnv* env = base::android::AttachCurrentThread();
if (!success) {
// The installation failed.
if (infobar())
infobar()->RemoveSelf();
Java_AppBannerInfoBarDelegateAndroid_showWebApkInstallFailureToast(env);
DVLOG(1) << "The WebAPK installation failed.";
return;
}
webapk_package_name_ = webapk_package_name;
ScopedJavaLocalRef<jstring> java_webapk_package_name =
base::android::ConvertUTF8ToJavaString(env, webapk_package_name);
Java_AppBannerInfoBarDelegateAndroid_setWebApkInstallingState(
env, java_delegate_, false);
Java_AppBannerInfoBarDelegateAndroid_setWebApkPackageName(
env, java_delegate_, java_webapk_package_name);
UpdateInstallState(env, nullptr);
}
bool AppBannerInfoBarDelegateAndroid::LinkClicked(
WindowOpenDisposition disposition) {
if (native_app_data_.is_null())
......
......@@ -38,7 +38,8 @@ class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
const content::Manifest& manifest,
const GURL& icon_url,
std::unique_ptr<SkBitmap> icon,
int event_request_id);
int event_request_id,
bool is_webapk);
// Delegate for promoting an Android app.
AppBannerInfoBarDelegateAndroid(
......@@ -68,8 +69,12 @@ class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
private:
void CreateJavaDelegate();
bool AcceptNativeApp(content::WebContents* web_contents);
bool AcceptWebApp(content::WebContents* web_contents);
bool AcceptWebApk(content::WebContents* web_contents);
void SendBannerAccepted(content::WebContents* web_contents,
const std::string& platform);
void OnWebApkInstallFinished(bool success, const std::string& webapk_package);
// ConfirmInfoBarDelegate:
infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
......@@ -99,6 +104,11 @@ class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
int event_request_id_;
bool has_user_interaction_;
std::string webapk_package_name_;
bool is_webapk_;
base::WeakPtrFactory<AppBannerInfoBarDelegateAndroid> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(AppBannerInfoBarDelegateAndroid);
}; // AppBannerInfoBarDelegateAndroid
......
......@@ -187,13 +187,14 @@ void AppBannerManagerAndroid::ShowBanner() {
infobars::InfoBar* infobar = nullptr;
if (native_app_data_.is_null()) {
bool is_webapk = ChromeWebApkHost::AreWebApkEnabled();
std::unique_ptr<AppBannerInfoBarDelegateAndroid> delegate(
new AppBannerInfoBarDelegateAndroid(
GetWeakPtr(), app_title_, manifest_url_, manifest_, icon_url_,
std::move(icon_), event_request_id()));
std::move(icon_), event_request_id(), is_webapk));
infobar = new AppBannerInfoBarAndroid(std::move(delegate),
manifest_.start_url);
infobar = new AppBannerInfoBarAndroid(
std::move(delegate), manifest_.start_url, is_webapk);
if (infobar) {
RecordDidShowBanner("AppBanner.WebApp.Shown");
TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_CREATED);
......
......@@ -96,6 +96,7 @@
#include "chrome/browser/android/warmup_manager.h"
#include "chrome/browser/android/web_contents_factory.h"
#include "chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h"
#include "chrome/browser/android/webapk/webapk_installer.h"
#include "chrome/browser/android/webapk/webapk_update_manager.h"
#include "chrome/browser/android/webapps/add_to_homescreen_dialog_helper.h"
#include "chrome/browser/android/webapps/webapp_registry.h"
......@@ -367,6 +368,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"VrShell", vr_shell::RegisterVrShell},
#endif
{"WarmupManager", RegisterWarmupManager},
{"WebApkInstaller", WebApkInstaller::Register},
{"WebApkUpdateManager", WebApkUpdateManager::Register},
{"WebappRegistry", WebappRegistry::RegisterWebappRegistry},
{"WebContentsFactory", RegisterWebContentsFactory},
......
......@@ -73,10 +73,6 @@ void ShortcutHelper::AddToLauncherWithSkBitmap(
const base::Closure& splash_image_callback) {
if (info.display == blink::WebDisplayModeStandalone ||
info.display == blink::WebDisplayModeFullscreen) {
if (ChromeWebApkHost::AreWebApkEnabled()) {
InstallWebApkWithSkBitmap(browser_context, info, icon_bitmap);
return;
}
AddWebappWithSkBitmap(info, webapp_id, icon_bitmap, splash_image_callback);
return;
}
......@@ -87,11 +83,11 @@ void ShortcutHelper::AddToLauncherWithSkBitmap(
void ShortcutHelper::InstallWebApkWithSkBitmap(
content::BrowserContext* browser_context,
const ShortcutInfo& info,
const SkBitmap& icon_bitmap) {
const SkBitmap& icon_bitmap,
const WebApkInstaller::FinishCallback& callback) {
// WebApkInstaller destroys itself when it is done.
WebApkInstaller* installer = new WebApkInstaller(info, icon_bitmap);
installer->InstallAsync(browser_context,
base::Bind(&ShortcutHelper::OnBuiltWebApk));
installer->InstallAsync(browser_context, callback);
}
// static
......@@ -150,16 +146,6 @@ void ShortcutHelper::AddShortcutWithSkBitmap(
info.source);
}
void ShortcutHelper::OnBuiltWebApk(bool success) {
if (success) {
DVLOG(1) << "Sent request to install WebAPK. Seems to have worked.";
} else {
LOG(ERROR) << "WebAPK install failed.";
}
// TODO(pkotwicz): Figure out what to do when installing WebAPK fails.
// (crbug.com/626950)
}
int ShortcutHelper::GetIdealHomescreenIconSizeInDp() {
if (kIdealHomescreenIconSize == -1)
GetHomescreenIconAndSplashImageSizes();
......
......@@ -10,6 +10,7 @@
#include "base/callback_forward.h"
#include "base/macros.h"
#include "chrome/browser/android/shortcut_info.h"
#include "chrome/browser/android/webapk/webapk_installer.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace content {
......@@ -39,7 +40,8 @@ class ShortcutHelper {
static void InstallWebApkWithSkBitmap(
content::BrowserContext* browser_context,
const ShortcutInfo& info,
const SkBitmap& icon_bitmap);
const SkBitmap& icon_bitmap,
const WebApkInstaller::FinishCallback& callback);
// Adds a shortcut which opens in a fullscreen window to the launcher.
// |splash_image_callback| will be invoked once the Java-side operation has
......@@ -57,15 +59,6 @@ class ShortcutHelper {
const ShortcutInfo& info,
const SkBitmap& icon_bitmap);
// Called after either:
// - A request to install the WebAPK has been sent.
// OR
// - WebAPK creation process fails.
// |success| indicates whether an installation request was sent. A "true"
// value of |success| does not guarantee that the WebAPK will be successfully
// installed.
static void OnBuiltWebApk(bool success);
// Returns the ideal size for an icon representing a web app.
static int GetIdealHomescreenIconSizeInDp();
......
......@@ -143,9 +143,20 @@ WebApkInstaller::WebApkInstaller(const ShortcutInfo& shortcut_info,
GURL(command_line->HasSwitch(switches::kWebApkServerUrl)
? command_line->GetSwitchValueASCII(switches::kWebApkServerUrl)
: kDefaultWebApkServerUrl);
CreateJavaRef();
}
WebApkInstaller::~WebApkInstaller() {}
void WebApkInstaller::CreateJavaRef() {
JNIEnv* env = base::android::AttachCurrentThread();
java_ref_.Reset(Java_WebApkInstaller_create(
env, reinterpret_cast<intptr_t>(this)));
}
WebApkInstaller::~WebApkInstaller() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_WebApkInstaller_destroy(env, java_ref_);
java_ref_.Reset();
}
void WebApkInstaller::InstallAsync(content::BrowserContext* browser_context,
const FinishCallback& finish_callback) {
......@@ -172,11 +183,6 @@ void WebApkInstaller::InstallAsyncWithURLRequestContextGetter(
DownloadAppIconAndComputeMurmur2Hash();
}
void WebApkInstaller::SetTimeoutMs(int timeout_ms) {
webapk_download_url_timeout_ms_ = timeout_ms;
download_timeout_ms_ = timeout_ms;
}
void WebApkInstaller::UpdateAsync(content::BrowserContext* browser_context,
const FinishCallback& finish_callback,
const std::string& icon_murmur2_hash,
......@@ -208,20 +214,40 @@ void WebApkInstaller::UpdateAsyncWithURLRequestContextGetter(
weak_ptr_factory_.GetWeakPtr()));
}
void WebApkInstaller::SetTimeoutMs(int timeout_ms) {
webapk_download_url_timeout_ms_ = timeout_ms;
download_timeout_ms_ = timeout_ms;
}
void WebApkInstaller::OnInstallFinished(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jboolean success) {
if (success)
OnSuccess();
else
OnFailure();
}
// static
bool WebApkInstaller::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
bool WebApkInstaller::StartInstallingDownloadedWebApk(
JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& java_file_path,
const base::android::ScopedJavaLocalRef<jstring>& java_package_name) {
return Java_WebApkInstaller_installAsyncFromNative(env, java_file_path,
java_package_name);
return Java_WebApkInstaller_installAsyncFromNative(
env, java_ref_, java_file_path, java_package_name);
}
bool WebApkInstaller::StartUpdateUsingDownloadedWebApk(
JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& java_file_path,
const base::android::ScopedJavaLocalRef<jstring>& java_package_name) {
return Java_WebApkInstaller_updateAsyncFromNative(env, java_file_path,
java_package_name);
return Java_WebApkInstaller_updateAsyncFromNative(
env, java_ref_, java_file_path, java_package_name);
}
void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
......@@ -287,7 +313,7 @@ void WebApkInstaller::OnGotIconMurmur2Hash(
base::Bind(&BuildWebApkProtoInBackground, shortcut_info_,
shortcut_icon_, shortcut_icon_murmur2_hash_),
base::Bind(&WebApkInstaller::SendCreateWebApkRequest,
weak_ptr_factory_.GetWeakPtr()));
weak_ptr_factory_.GetWeakPtr()));
}
void WebApkInstaller::SendCreateWebApkRequest(
......@@ -325,6 +351,7 @@ void WebApkInstaller::OnGotWebApkDownloadUrl(const GURL& download_url,
const std::string& package_name) {
base::FilePath output_dir;
base::android::GetCacheDirectory(&output_dir);
webapk_package_ = package_name;
// TODO(pkotwicz): Download WebAPKs into WebAPK-specific subdirectory
// directory.
// TODO(pkotwicz): Figure out when downloaded WebAPK should be deleted.
......@@ -333,15 +360,14 @@ void WebApkInstaller::OnGotWebApkDownloadUrl(const GURL& download_url,
FROM_HERE, base::TimeDelta::FromMilliseconds(download_timeout_ms_),
base::Bind(&WebApkInstaller::OnTimeout, weak_ptr_factory_.GetWeakPtr()));
base::FilePath output_path = output_dir.AppendASCII(package_name);
base::FilePath output_path = output_dir.AppendASCII(webapk_package_);
downloader_.reset(new FileDownloader(
download_url, output_path, true, request_context_getter_,
base::Bind(&WebApkInstaller::OnWebApkDownloaded,
weak_ptr_factory_.GetWeakPtr(), output_path, package_name)));
weak_ptr_factory_.GetWeakPtr(), output_path)));
}
void WebApkInstaller::OnWebApkDownloaded(const base::FilePath& file_path,
const std::string& package_name,
FileDownloader::Result result) {
timer_.Stop();
......@@ -358,12 +384,11 @@ void WebApkInstaller::OnWebApkDownloaded(const base::FilePath& file_path,
GetBackgroundTaskRunner().get(), FROM_HERE,
base::Bind(&base::SetPosixFilePermissions, file_path, posix_permissions),
base::Bind(&WebApkInstaller::OnWebApkMadeWorldReadable,
weak_ptr_factory_.GetWeakPtr(), file_path, package_name));
weak_ptr_factory_.GetWeakPtr(), file_path));
}
void WebApkInstaller::OnWebApkMadeWorldReadable(
const base::FilePath& file_path,
const std::string& package_name,
bool change_permission_success) {
if (!change_permission_success) {
OnFailure();
......@@ -374,7 +399,7 @@ void WebApkInstaller::OnWebApkMadeWorldReadable(
base::android::ScopedJavaLocalRef<jstring> java_file_path =
base::android::ConvertUTF8ToJavaString(env, file_path.value());
base::android::ScopedJavaLocalRef<jstring> java_package_name =
base::android::ConvertUTF8ToJavaString(env, package_name);
base::android::ConvertUTF8ToJavaString(env, webapk_package_);
bool success = false;
if (task_type_ == INSTALL) {
success = StartInstallingDownloadedWebApk(env, java_file_path,
......@@ -383,9 +408,7 @@ void WebApkInstaller::OnWebApkMadeWorldReadable(
success = StartUpdateUsingDownloadedWebApk(env, java_file_path,
java_package_name);
}
if (success)
OnSuccess();
else
if (!success)
OnFailure();
}
......@@ -396,11 +419,11 @@ void WebApkInstaller::OnTimeout() {
void WebApkInstaller::OnSuccess() {
FinishCallback callback = finish_callback_;
delete this;
callback.Run(true);
callback.Run(true, webapk_package_);
}
void WebApkInstaller::OnFailure() {
FinishCallback callback = finish_callback_;
delete this;
callback.Run(false);
callback.Run(false, "");
}
......@@ -34,14 +34,15 @@ class WebApk;
class WebApkIconHasher;
// Talks to Chrome WebAPK server and Google Play to generate a WebAPK on the
// server, download it, and install it.
// server, download it, and install it. The native WebApkInstaller owns the
// Java WebApkInstaller counterpart.
class WebApkInstaller : public net::URLFetcherDelegate {
public:
// Called when either a request for creating/updating a WebAPK has been sent
// to Google Play or the create/update process fails.
// Called when the creation/updating of a WebAPK is finished or failed.
// Parameters:
// - whether the request succeeds.
using FinishCallback = base::Callback<void(bool)>;
// - whether the process succeeds.
// - the package name of the WebAPK.
using FinishCallback = base::Callback<void(bool, const std::string&)>;
WebApkInstaller(const ShortcutInfo& shortcut_info,
const SkBitmap& shorcut_icon);
......@@ -52,12 +53,12 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Google Play to install the downloaded WebAPK. Calls |callback| after the
// request to install the WebAPK is sent to Google Play.
void InstallAsync(content::BrowserContext* browser_context,
const FinishCallback& callback);
const FinishCallback& finish_callback);
// Same as InstallAsync() but uses the passed in |request_context_getter|.
void InstallAsyncWithURLRequestContextGetter(
net::URLRequestContextGetter* request_context_getter,
const FinishCallback& callback);
const FinishCallback& finish_callback);
// Talks to the Chrome WebAPK server to update a WebAPK on the server and to
// the Google Play server to install the downloaded WebAPK. Calls |callback|
......@@ -79,6 +80,14 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Sets the timeout for the server requests.
void SetTimeoutMs(int timeout_ms);
// Called once the installation is complete or failed.
void OnInstallFinished(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jboolean success);
// Registers JNI hooks.
static bool Register(JNIEnv* env);
protected:
// Starts installation of the downloaded WebAPK. Returns whether the install
// could be started. The installation may still fail if true is returned.
......@@ -98,6 +107,9 @@ class WebApkInstaller : public net::URLFetcherDelegate {
const base::android::ScopedJavaLocalRef<jstring>& java_file_path,
const base::android::ScopedJavaLocalRef<jstring>& java_package_name);
// Called when the request to install the WebAPK is sent to Google Play.
void OnSuccess();
private:
enum TaskType {
UNDEFINED,
......@@ -105,6 +117,9 @@ class WebApkInstaller : public net::URLFetcherDelegate {
UPDATE,
};
// Create the Java object.
void CreateJavaRef();
// net::URLFetcherDelegate:
void OnURLFetchComplete(const net::URLFetcher* source) override;
......@@ -139,28 +154,21 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Called once the WebAPK has been downloaded. Makes the downloaded WebAPK
// world readable and installs the WebAPK if the download was successful.
// |file_path| is the file path that the WebAPK was downloaded to.
// |package_name| is the package name that the WebAPK should be installed at.
void OnWebApkDownloaded(const base::FilePath& file_path,
const std::string& package_name,
FileDownloader::Result result);
// Called once the downloaded WebAPK has been made world readable. Installs
// the WebAPK.
// |file_path| is the file path that the WebAPK was downloaded to.
// |package_name| is the package name that the WebAPK should be installed at.
// |change_permission_success| is whether the WebAPK could be made world
// readable.
void OnWebApkMadeWorldReadable(const base::FilePath& file_path,
const std::string& package_name,
bool change_permission_success);
// Called when the request to the WebAPK server times out or when the WebAPK
// download times out.
void OnTimeout();
// Called when the request to install the WebAPK is sent to Google Play.
void OnSuccess();
// Called if a WebAPK could not be created. WebApkInstaller only tracks the
// WebAPK creation and the WebAPK download. It does not track the
// WebAPK installation. OnFailure() is not called if the WebAPK could not be
......@@ -214,6 +222,9 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Indicates whether the installer is for installing or updating a WebAPK.
TaskType task_type_;
// Points to the Java Object.
base::android::ScopedJavaGlobalRef<jobject> java_ref_;
// Used to get |weak_ptr_|.
base::WeakPtrFactory<WebApkInstaller> weak_ptr_factory_;
......
......@@ -66,6 +66,7 @@ class TestWebApkInstaller : public WebApkInstaller {
JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& file_path,
const base::android::ScopedJavaLocalRef<jstring>& package_name) override {
PostTaskToRunSuccessCallback();
return true;
}
......@@ -73,9 +74,16 @@ class TestWebApkInstaller : public WebApkInstaller {
JNIEnv* env,
const base::android::ScopedJavaLocalRef<jstring>& file_path,
const base::android::ScopedJavaLocalRef<jstring>& package_name) override {
PostTaskToRunSuccessCallback();
return true;
}
void PostTaskToRunSuccessCallback() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&TestWebApkInstaller::OnSuccess, base::Unretained(this)));
}
private:
DISALLOW_COPY_AND_ASSIGN(TestWebApkInstaller);
};
......@@ -134,7 +142,7 @@ class WebApkInstallerRunner {
bool success() { return success_; }
private:
void OnCompleted(bool success) {
void OnCompleted(bool success, const std::string& webapk_package) {
success_ = success;
on_completed_callback_.Run();
}
......
......@@ -25,8 +25,8 @@ bool WebApkUpdateManager::Register(JNIEnv* env) {
}
// static
void WebApkUpdateManager::OnBuiltWebApk(const std::string& webapk_package,
bool success) {
void WebApkUpdateManager::OnBuiltWebApk(bool success,
const std::string& webapk_package) {
JNIEnv* env = base::android::AttachCurrentThread();
if (success) {
......@@ -96,6 +96,6 @@ static void UpdateAsync(JNIEnv* env,
WebApkInstaller* installer = new WebApkInstaller(info, icon_bitmap);
installer->UpdateAsync(
profile,
base::Bind(&WebApkUpdateManager::OnBuiltWebApk, webapk_package),
base::Bind(&WebApkUpdateManager::OnBuiltWebApk),
icon_murmur2_hash, webapk_package, java_webapk_version);
}
......@@ -21,7 +21,7 @@ class WebApkUpdateManager {
// |success| indicates whether the request was issued to the server. A "true"
// value of |success| does not guarantee that the WebAPK will be successfully
// updated.
static void OnBuiltWebApk(const std::string& webapk_package, bool success);
static void OnBuiltWebApk(bool success, const std::string& webapk_package);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(WebApkUpdateManager);
......
......@@ -22,8 +22,11 @@ AppBannerInfoBarAndroid::AppBannerInfoBarAndroid(
AppBannerInfoBarAndroid::AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const GURL& app_url)
: ConfirmInfoBar(std::move(delegate)), app_url_(app_url) {}
const GURL& app_url,
bool is_webapk)
: ConfirmInfoBar(std::move(delegate)),
app_url_(app_url),
is_webapk_(is_webapk) {}
AppBannerInfoBarAndroid::~AppBannerInfoBarAndroid() {
}
......@@ -57,7 +60,7 @@ AppBannerInfoBarAndroid::CreateRenderInfoBar(JNIEnv* env) {
base::android::ConvertUTF8ToJavaString(env, trimmed_url);
infobar.Reset(Java_AppBannerInfoBarAndroid_createWebAppInfoBar(
env, app_title, java_bitmap, app_url));
env, app_title, java_bitmap, app_url, is_webapk_));
}
java_infobar_.Reset(env, infobar.obj());
......
......@@ -25,7 +25,8 @@ class AppBannerInfoBarAndroid : public ConfirmInfoBar {
// Constructs an AppBannerInfoBarAndroid promoting a web app.
AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const GURL& app_url);
const GURL& app_url,
bool is_webapk);
~AppBannerInfoBarAndroid() override;
......@@ -45,6 +46,9 @@ class AppBannerInfoBarAndroid : public ConfirmInfoBar {
// Web app: URL for the app.
GURL app_url_;
// Indicates whether the info bar is for installing a WebAPK.
bool is_webapk_;
base::android::ScopedJavaGlobalRef<jobject> java_infobar_;
DISALLOW_COPY_AND_ASSIGN(AppBannerInfoBarAndroid);
......
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