Commit bfbb56d2 authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android WebAPK] Store WebAPK update data to disk

This CL makes WebApkUpdateManager store the "WebAPK update request" to disk and
retrieve it from disk once the WebApkActivity is ready to do the update (Once
the WebApkActivity is in the background). This CL is in preparation for
https://codereview.chromium.org/2948713002/ The "WebAPK update request" cannot
be encoded as an extra on com.google.android.gms.gcm.Task#setExtras() because
GCMNetworkManager limits the total size of the extras to 10KB. We might send up
to two PNG images as part of an update request

BUG=713655

Change-Id: I9d86eb8ffe4f650340028ec6c4d12a7b8ddc1aec
Reviewed-on: https://chromium-review.googlesource.com/581231
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#506451}
parent 6869b788
......@@ -50,11 +50,8 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
/** Data extracted from the WebAPK's launch intent and from the WebAPK's Android Manifest. */
private WebApkInfo mInfo;
/**
* The cached data for a pending update request which needs to be sent after the WebAPK isn't
* running in the foreground.
*/
private PendingUpdate mPendingUpdate;
/** Whether an update request should be made once the WebAPK is backgrounded. */
private boolean mHasPendingUpdate;
/** The WebApkActivity which owns the WebApkUpdateManager. */
private final WebApkActivity mActivity;
......@@ -67,20 +64,6 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
/** Runs failure callback if WebAPK update is not scheduled within deadline. */
private Handler mUpdateFailureHandler;
/**
* Contains all the data which is cached for a pending update request once the WebAPK is no
* longer running foreground.
*/
private static class PendingUpdate {
public WebApkInfo mUpdateInfo;
byte[] mSerializedProto;
public PendingUpdate(WebApkInfo info, byte[] serializedProto) {
mUpdateInfo = info;
mSerializedProto = serializedProto;
}
}
/** Called with update result. */
private static interface WebApkUpdateCallback {
@CalledByNative("WebApkUpdateCallback")
......@@ -119,8 +102,9 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
* @return Whether a pending update request is sent to the WebAPK server.
*/
public boolean requestPendingUpdate() {
if (mPendingUpdate != null) {
updateAsync(mPendingUpdate.mUpdateInfo, mPendingUpdate.mSerializedProto);
String updateRequestPath = mStorage.getPendingUpdateRequestPath();
if (mHasPendingUpdate && updateRequestPath != null) {
updateAsync(updateRequestPath);
return true;
}
return false;
......@@ -131,7 +115,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
}
public boolean getHasPendingUpdateForTesting() {
return mPendingUpdate != null;
return mHasPendingUpdate;
}
public static void setUpdatesEnabledForTesting(boolean enabled) {
......@@ -180,7 +164,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
recordUpdate(WebApkInstallResult.FAILURE, false /* relaxUpdates*/);
if (fetchedInfo != null) {
buildProtoAndScheduleUpdate(
buildUpdateRequestAndSchedule(
fetchedInfo, primaryIconUrl, badgeIconUrl, false /* isManifestStale */);
return;
}
......@@ -188,7 +172,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
// Tell the server that the our version of the Web Manifest might be stale and to ignore
// our Web Manifest data if the server's Web Manifest data is newer. This scenario can
// occur if the Web Manifest is temporarily unreachable.
buildProtoAndScheduleUpdate(
buildUpdateRequestAndSchedule(
mInfo, "" /* primaryIconUrl */, "" /* badgeIconUrl */, true /* isManifestStale */);
}
......@@ -200,7 +184,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
}
/** Builds proto to send to the WebAPK server. */
protected void buildProtoAndScheduleUpdate(final WebApkInfo info, String primaryIconUrl,
protected void buildUpdateRequestAndSchedule(final WebApkInfo info, String primaryIconUrl,
String badgeIconUrl, boolean isManifestStale) {
int versionCode = readVersionCodeFromAndroidManifest(info.webApkPackageName());
int size = info.iconUrlToMurmur2HashMap().size();
......@@ -214,28 +198,37 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
i++;
}
Callback<byte[]> callback = new Callback<byte[]>() {
final String updateRequestPath = mStorage.createAndSetUpdateRequestFilePath(info);
Callback<Boolean> callback = new Callback<Boolean>() {
@Override
public void onResult(byte[] result) {
scheduleUpdate(info, result);
public void onResult(Boolean success) {
if (!success) {
recordUpdate(WebApkInstallResult.FAILURE, false /* relaxUpdates*/);
mStorage.updateLastRequestedShellApkVersion(
WebApkVersion.CURRENT_SHELL_APK_VERSION);
mStorage.deletePendingUpdateRequestFile();
return;
}
scheduleUpdate(updateRequestPath);
}
};
nativeBuildUpdateWebApkProto(info.manifestStartUrl(), info.scopeUri().toString(),
info.name(), info.shortName(), primaryIconUrl, info.icon(), badgeIconUrl,
info.badgeIcon(), iconUrls, iconHashes, info.displayMode(), info.orientation(),
info.themeColor(), info.backgroundColor(), info.manifestUrl(),
info.webApkPackageName(), versionCode, isManifestStale, callback);
nativeStoreWebApkUpdateRequestToFile(updateRequestPath, info.manifestStartUrl(),
info.scopeUri().toString(), info.name(), info.shortName(), primaryIconUrl,
info.icon(), badgeIconUrl, info.badgeIcon(), iconUrls, iconHashes,
info.displayMode(), info.orientation(), info.themeColor(), info.backgroundColor(),
info.manifestUrl(), info.webApkPackageName(), versionCode, isManifestStale,
callback);
}
/**
* Sends update request to WebAPK Server if the WebAPK is running in the background; caches the
* fetched WebApkInfo otherwise.
* Sends update request to WebAPK Server if the WebAPK is running in the background; sets
* update as "pending" otherwise.
*/
protected void scheduleUpdate(WebApkInfo info, byte[] serializedProto) {
protected void scheduleUpdate(String updateRequestPath) {
int numberOfUpdateRequests = mStorage.getUpdateRequests();
boolean forceUpdateNow = numberOfUpdateRequests >= MAX_UPDATE_ATTEMPTS;
if (!isInForeground() || forceUpdateNow) {
updateAsync(info, serializedProto);
updateAsync(updateRequestPath);
WebApkUma.recordUpdateRequestSent(WebApkUma.UPDATE_REQUEST_SENT_FIRST_TRY);
return;
}
......@@ -244,7 +237,7 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
// The {@link numberOfUpdateRequests} can never exceed 2 here (otherwise we'll have taken
// the branch above and have returned before reaching this statement).
WebApkUma.recordUpdateRequestQueued(numberOfUpdateRequests);
mPendingUpdate = new PendingUpdate(info, serializedProto);
mHasPendingUpdate = true;
}
/** Returns whether the associated WebApkActivity is running in foreground. */
......@@ -256,29 +249,26 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
/**
* Sends update request to the WebAPK Server and cleanup.
*/
private void updateAsync(WebApkInfo info, byte[] serializedProto) {
updateAsyncImpl(info, serializedProto);
private void updateAsync(String updateRequestPath) {
updateAsyncImpl(updateRequestPath);
mStorage.resetUpdateRequests();
mPendingUpdate = null;
mHasPendingUpdate = false;
}
/**
* Sends update request to the WebAPK Server.
*/
protected void updateAsyncImpl(WebApkInfo info, byte[] serializedProto) {
if (info == null || serializedProto == null) {
return;
}
protected void updateAsyncImpl(String updateRequestPath) {
WebApkUpdateCallback callback = new WebApkUpdateCallback() {
@Override
public void onResultFromNative(@WebApkInstallResult int result, boolean relaxUpdates) {
recordUpdate(result, relaxUpdates);
mStorage.updateLastRequestedShellApkVersion(
WebApkVersion.CURRENT_SHELL_APK_VERSION);
mStorage.deletePendingUpdateRequestFile();
}
};
nativeUpdateWebApk(info.webApkPackageName(), info.shortName(), serializedProto, callback);
nativeUpdateWebApkFromFile(updateRequestPath, callback);
}
/**
......@@ -411,12 +401,12 @@ public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
return UrlUtilities.urlsMatchIgnoringFragments(url1, url2);
}
private static native void nativeBuildUpdateWebApkProto(String startUrl, String scope,
String name, String shortName, String primaryIconUrl, Bitmap primaryIcon,
String badgeIconUrl, Bitmap badgeIcon, String[] iconUrls, String[] iconHashes,
int displayMode, int orientation, long themeColor, long backgroundColor,
String manifestUrl, String webApkPackage, int webApkVersion, boolean isManifestStale,
Callback<byte[]> callback);
private static native void nativeUpdateWebApk(String webApkPackage, String shortName,
byte[] serializedProto, WebApkUpdateCallback callback);
private static native void nativeStoreWebApkUpdateRequestToFile(String updateRequestPath,
String startUrl, String scope, String name, String shortName, String primaryIconUrl,
Bitmap primaryIcon, String badgeIconUrl, Bitmap badgeIcon, String[] iconUrls,
String[] iconHashes, int displayMode, int orientation, long themeColor,
long backgroundColor, String manifestUrl, String webApkPackage, int webApkVersion,
boolean isManifestStale, Callback<Boolean> callback);
private static native void nativeUpdateWebApkFromFile(
String updateRequestPath, WebApkUpdateCallback callback);
}
......@@ -9,9 +9,12 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.blink_public.platform.WebDisplayMode;
import org.chromium.chrome.browser.ShortcutHelper;
......@@ -20,6 +23,7 @@ import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.content_public.common.ScreenOrientationValues;
import org.chromium.webapk.lib.common.WebApkConstants;
import java.io.File;
import java.util.concurrent.TimeUnit;
/**
......@@ -28,6 +32,10 @@ import java.util.concurrent.TimeUnit;
* track of web app data known to Chrome.
*/
public class WebappDataStorage {
private static final String TAG = "WebappDataStorage";
/** Path of subdirectory within cache directory which contains data for pending updates. */
static final String UPDATE_DIRECTORY_PATH = "webapk/update";
static final String SHARED_PREFS_FILE_PREFIX = "webapp_";
static final String KEY_SPLASH_ICON = "splash_icon";
......@@ -70,6 +78,9 @@ public class WebappDataStorage {
// Whether the user has dismissed the disclosure UI.
static final String KEY_DISMISSED_DISCLOSURE = "dismissed_dislosure";
// The path where serialized update data is written before uploading to the WebAPK server.
static final String KEY_PENDING_UPDATE_FILE_PATH = "pending_update_file_path";
// Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed.
public static final long UPDATE_INTERVAL = TimeUnit.DAYS.toMillis(3L);
......@@ -304,6 +315,7 @@ public class WebappDataStorage {
* file. This does NOT delete the file itself but the file is left empty.
*/
void delete() {
deletePendingUpdateRequestFile();
mPreferences.edit().clear().apply();
}
......@@ -312,6 +324,8 @@ public class WebappDataStorage {
* This does not remove the stored splash screen image (if any) for the app.
*/
void clearHistory() {
deletePendingUpdateRequestFile();
SharedPreferences.Editor editor = mPreferences.edit();
editor.remove(KEY_LAST_USED);
......@@ -507,6 +521,44 @@ public class WebappDataStorage {
return mPreferences.getBoolean(KEY_RELAX_UPDATES, false);
}
/**
* Returns file where WebAPK update data should be stored and stores the file name in
* SharedPreferences.
*/
String createAndSetUpdateRequestFilePath(WebApkInfo info) {
String filePath =
new File(new File(PathUtils.getCacheDirectory(), UPDATE_DIRECTORY_PATH), info.id())
.getPath();
mPreferences.edit().putString(KEY_PENDING_UPDATE_FILE_PATH, filePath).apply();
return filePath;
}
/** Returns the path of the file which contains data to update the WebAPK. */
@Nullable
String getPendingUpdateRequestPath() {
return mPreferences.getString(KEY_PENDING_UPDATE_FILE_PATH, null);
}
/**
* Deletes the file which contains data to update the WebAPK. The file is large (> 1Kb) and
* should be deleted when the update completes.
*/
void deletePendingUpdateRequestFile() {
final String pendingUpdateFilePath = getPendingUpdateRequestPath();
if (pendingUpdateFilePath == null) return;
mPreferences.edit().remove(KEY_PENDING_UPDATE_FILE_PATH).apply();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (!new File(pendingUpdateFilePath).delete()) {
Log.d(TAG, "Failed to delete file " + pendingUpdateFilePath);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/** Returns whether we should check for update. */
boolean shouldCheckForUpdate() {
long checkUpdatesInterval =
......
......@@ -86,7 +86,7 @@ public class WebApkUpdateManagerTest {
}
@Override
protected void buildProtoAndScheduleUpdate(final WebApkInfo info, String primaryIconUrl,
protected void buildUpdateRequestAndSchedule(final WebApkInfo info, String primaryIconUrl,
String badgeIconUrl, boolean isManifestStale) {
mNeedsUpdate = true;
}
......
......@@ -27,6 +27,7 @@ import org.robolectric.shadows.ShadowBitmap;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.PathUtils;
import org.chromium.blink_public.platform.WebDisplayMode;
import org.chromium.chrome.browser.DisableHistogramsRule;
import org.chromium.chrome.browser.ShortcutHelper;
......@@ -102,6 +103,7 @@ public class WebApkUpdateManagerTest {
}
private static class TestWebApkUpdateManager extends WebApkUpdateManager {
private WebappDataStorage mStorage;
private TestWebApkUpdateDataFetcher mFetcher;
private boolean mUpdateRequested;
private String mUpdateName;
......@@ -110,6 +112,7 @@ public class WebApkUpdateManagerTest {
public TestWebApkUpdateManager(WebappDataStorage storage) {
super(null, storage);
mStorage = storage;
}
/**
......@@ -144,14 +147,15 @@ public class WebApkUpdateManagerTest {
}
@Override
protected void buildProtoAndScheduleUpdate(WebApkInfo info, String primaryIconUrl,
protected void buildUpdateRequestAndSchedule(WebApkInfo info, String primaryIconUrl,
String badgeIconUrl, boolean isManifestStale) {
mUpdateName = info.name();
scheduleUpdate(info, new byte[0]);
String updateRequestPath = mStorage.createAndSetUpdateRequestFilePath(info);
scheduleUpdate(updateRequestPath);
}
@Override
protected void updateAsyncImpl(WebApkInfo info, byte[] serializedProto) {
protected void updateAsyncImpl(String updateRequestPath) {
mUpdateRequested = true;
}
......@@ -336,6 +340,7 @@ public class WebApkUpdateManagerTest {
@Before
public void setUp() {
ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
PathUtils.setPrivateDataDirectorySuffix("chrome");
CommandLine.init(null);
registerWebApk(WEBAPK_PACKAGE_NAME, defaultManifestData(), CURRENT_SHELL_APK_VERSION);
......
......@@ -7,6 +7,7 @@
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/shortcut_info.h"
......@@ -59,12 +60,10 @@ void WebApkInstallService::InstallAsync(content::WebContents* web_contents,
}
void WebApkInstallService::UpdateAsync(
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const base::FilePath& update_request_path,
const FinishCallback& finish_callback) {
WebApkInstaller::UpdateAsync(browser_context_, webapk_package, short_name,
std::move(serialized_proto), finish_callback);
WebApkInstaller::UpdateAsync(browser_context_, update_request_path,
finish_callback);
}
void WebApkInstallService::OnFinishedInstall(
......
......@@ -20,6 +20,10 @@
#include "content/public/browser/web_contents_observer.h"
#include "url/gurl.h"
namespace base {
class FilePath;
}
namespace content {
class BrowserContext;
class WebContents;
......@@ -70,11 +74,10 @@ class WebApkInstallService : public KeyedService {
webapk::InstallSource install_source);
// 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
// |finish_callback| once the update completed or failed.
void UpdateAsync(const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
// the Google Play server to install the downloaded WebAPK.
// |update_request_path| is the path of the file with the update request.
// Calls |finish_callback| once the update completed or failed.
void UpdateAsync(const base::FilePath& update_request_path,
const FinishCallback& finish_callback);
private:
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/android/webapk/webapk_installer.h"
#include <utility>
#include <vector>
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
......@@ -12,6 +13,8 @@
#include "base/android/path_utils.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
......@@ -20,7 +23,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/webapk/chrome_webapk_host.h"
......@@ -112,7 +115,7 @@ void SetImageData(webapk::Image* image, const SkBitmap& icon) {
// Populates webapk::WebApk and returns it.
// Must be called on a worker thread because it encodes an SkBitmap.
std::unique_ptr<std::vector<uint8_t>> BuildProtoInBackground(
std::unique_ptr<std::string> BuildProtoInBackground(
const ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
......@@ -175,14 +178,47 @@ std::unique_ptr<std::vector<uint8_t>> BuildProtoInBackground(
image->set_hash(entry.second);
}
size_t serialized_size = webapk->ByteSize();
std::unique_ptr<std::vector<uint8_t>> serialized_proto =
base::MakeUnique<std::vector<uint8_t>>();
serialized_proto->resize(serialized_size);
webapk->SerializeToArray(serialized_proto->data(), serialized_size);
std::unique_ptr<std::string> serialized_proto =
base::MakeUnique<std::string>();
webapk->SerializeToString(serialized_proto.get());
return serialized_proto;
}
// Builds the WebAPK proto for an update request and stores it to
// |update_request_path|. Returns whether the proto was successfully written to
// disk.
bool StoreUpdateRequestToFileInBackground(
const base::FilePath& update_request_path,
const ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
const std::string& package_name,
const std::string& version,
const std::map<std::string, std::string>& icon_url_to_murmur2_hash,
bool is_manifest_stale) {
base::AssertBlockingAllowed();
std::unique_ptr<std::string> proto = BuildProtoInBackground(
shortcut_info, primary_icon, badge_icon, package_name, version,
icon_url_to_murmur2_hash, is_manifest_stale);
// Create directory if it does not exist.
base::CreateDirectory(update_request_path.DirName());
int bytes_written = base::WriteFile(update_request_path,
proto->c_str(),
proto->size());
return (bytes_written == static_cast<int>(proto->size()));
}
// Reads |file| and returns contents. Must be called on a background thread.
std::unique_ptr<std::string> ReadFileInBackground(const base::FilePath& file) {
base::AssertBlockingAllowed();
std::unique_ptr<std::string> update_request = base::MakeUnique<std::string>();
base::ReadFileToString(file, update_request.get());
return update_request;
}
// Returns task runner for running background tasks.
scoped_refptr<base::TaskRunner> GetBackgroundTaskRunner() {
return base::CreateTaskRunnerWithTraits(
......@@ -211,16 +247,12 @@ void WebApkInstaller::InstallAsync(content::BrowserContext* context,
}
// static
void WebApkInstaller::UpdateAsync(
content::BrowserContext* context,
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const FinishCallback& finish_callback) {
void WebApkInstaller::UpdateAsync(content::BrowserContext* context,
const base::FilePath& update_request_path,
const FinishCallback& finish_callback) {
// The installer will delete itself when it is done.
WebApkInstaller* installer = new WebApkInstaller(context);
installer->UpdateAsync(webapk_package, short_name,
std::move(serialized_proto), finish_callback);
installer->UpdateAsync(update_request_path, finish_callback);
}
// static
......@@ -235,12 +267,9 @@ void WebApkInstaller::InstallAsyncForTesting(WebApkInstaller* installer,
// static
void WebApkInstaller::UpdateAsyncForTesting(
WebApkInstaller* installer,
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const base::FilePath& update_request_path,
const FinishCallback& finish_callback) {
installer->UpdateAsync(webapk_package, short_name,
std::move(serialized_proto), finish_callback);
installer->UpdateAsync(update_request_path, finish_callback);
}
void WebApkInstaller::SetTimeoutMs(int timeout_ms) {
......@@ -263,8 +292,7 @@ void WebApkInstaller::BuildProto(
const std::string& version,
const std::map<std::string, std::string>& icon_url_to_murmur2_hash,
bool is_manifest_stale,
const base::Callback<void(std::unique_ptr<std::vector<uint8_t>>)>&
callback) {
const base::Callback<void(std::unique_ptr<std::string>)>& callback) {
base::PostTaskAndReplyWithResult(
GetBackgroundTaskRunner().get(), FROM_HERE,
base::Bind(&BuildProtoInBackground, shortcut_info, primary_icon,
......@@ -273,6 +301,25 @@ void WebApkInstaller::BuildProto(
callback);
}
// static
void WebApkInstaller::StoreUpdateRequestToFile(
const base::FilePath& update_request_path,
const ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
const std::string& package_name,
const std::string& version,
const std::map<std::string, std::string>& icon_url_to_murmur2_hash,
bool is_manifest_stale,
const base::Callback<void(bool)> callback) {
base::PostTaskAndReplyWithResult(
GetBackgroundTaskRunner().get(), FROM_HERE,
base::Bind(&StoreUpdateRequestToFileInBackground, update_request_path,
shortcut_info, primary_icon, badge_icon, package_name, version,
icon_url_to_murmur2_hash, is_manifest_stale),
callback);
}
void WebApkInstaller::InstallOrUpdateWebApk(const std::string& package_name,
int version,
const std::string& token) {
......@@ -360,22 +407,30 @@ void WebApkInstaller::InstallAsync(const ShortcutInfo& shortcut_info,
weak_ptr_factory_.GetWeakPtr()));
}
void WebApkInstaller::UpdateAsync(
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const FinishCallback& finish_callback) {
webapk_package_ = webapk_package;
short_name_ = short_name;
void WebApkInstaller::UpdateAsync(const base::FilePath& update_request_path,
const FinishCallback& finish_callback) {
finish_callback_ = finish_callback;
task_type_ = UPDATE;
if (!serialized_proto || serialized_proto->empty()) {
base::PostTaskAndReplyWithResult(
GetBackgroundTaskRunner().get(), FROM_HERE,
base::Bind(&ReadFileInBackground, update_request_path),
base::Bind(&WebApkInstaller::OnReadUpdateRequest,
weak_ptr_factory_.GetWeakPtr()));
}
void WebApkInstaller::OnReadUpdateRequest(
std::unique_ptr<std::string> update_request) {
std::unique_ptr<webapk::WebApk> proto(new webapk::WebApk);
if (update_request->empty() || !proto->ParseFromString(*update_request)) {
OnResult(WebApkInstallResult::FAILURE);
return;
}
SendRequest(std::move(serialized_proto));
webapk_package_ = proto->package_name();
short_name_ = base::UTF8ToUTF16(proto->manifest().short_name());
SendRequest(std::move(update_request));
}
void WebApkInstaller::OnURLFetchComplete(const net::URLFetcher* source) {
......@@ -468,19 +523,16 @@ void WebApkInstaller::OnGotBadgeIconMurmur2Hash(
}
void WebApkInstaller::SendRequest(
std::unique_ptr<std::vector<uint8_t>> serialized_proto) {
std::unique_ptr<std::string> serialized_proto) {
timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(webapk_server_timeout_ms_),
base::Bind(&WebApkInstaller::OnResult, weak_ptr_factory_.GetWeakPtr(),
WebApkInstallResult::FAILURE));
std::string serialized_proto_string(serialized_proto->begin(),
serialized_proto->end());
url_fetcher_ =
net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this);
url_fetcher_->SetRequestContext(request_context_getter_);
url_fetcher_->SetUploadData(kProtoMimeType, serialized_proto_string);
url_fetcher_->SetUploadData(kProtoMimeType, *serialized_proto);
url_fetcher_->SetLoadFlags(
net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA);
......
......@@ -8,7 +8,6 @@
#include <jni.h>
#include <map>
#include <memory>
#include <vector>
#include "base/android/scoped_java_ref.h"
#include "base/callback.h"
......@@ -24,6 +23,7 @@
namespace base {
class ElapsedTimer;
class FilePath;
}
namespace content {
......@@ -51,12 +51,10 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Creates a self-owned WebApkInstaller instance and talks to the Chrome
// WebAPK server to update a WebAPK on the server and locally requests the
// APK to be installed. Calls |callback| once the install completed or failed.
static void UpdateAsync(
content::BrowserContext* context,
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const FinishCallback& callback);
// |update_request_path| is the path of the file with the update request.
static void UpdateAsync(content::BrowserContext* context,
const base::FilePath& update_request_path,
const FinishCallback& callback);
// Calls the private function |InstallAsync| for testing.
// Should be used only for testing.
......@@ -64,16 +62,13 @@ class WebApkInstaller : public net::URLFetcherDelegate {
const ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
const FinishCallback& finish_callback);
const FinishCallback& callback);
// Calls the private function |UpdateAsync| for testing.
// Should be used only for testing.
static void UpdateAsyncForTesting(
WebApkInstaller* installer,
const std::string& webapk_package,
const base::string16& short_name,
std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const FinishCallback& callback);
static void UpdateAsyncForTesting(WebApkInstaller* installer,
const base::FilePath& update_request_path,
const FinishCallback& callback);
// Sets the timeout for the server requests.
void SetTimeoutMs(int timeout_ms);
......@@ -93,8 +88,21 @@ class WebApkInstaller : public net::URLFetcherDelegate {
const std::string& version,
const std::map<std::string, std::string>& icon_url_to_murmur2_hash,
bool is_manifest_stale,
const base::Callback<void(std::unique_ptr<std::vector<uint8_t>>)>&
callback);
const base::Callback<void(std::unique_ptr<std::string>)>& callback);
// Builds the WebAPK proto for an update or an install request and stores it
// to |update_request_path|. Runs |callback| with a boolean indicating
// whether the proto was successfully written to disk.
static void StoreUpdateRequestToFile(
const base::FilePath& update_request_path,
const ShortcutInfo& shortcut_info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
const std::string& package_name,
const std::string& version,
const std::map<std::string, std::string>& icon_url_to_murmur2_hash,
bool is_manifest_stale,
const base::Callback<void(bool)> callback);
protected:
explicit WebApkInstaller(content::BrowserContext* browser_context);
......@@ -127,12 +135,14 @@ class WebApkInstaller : public net::URLFetcherDelegate {
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
// |finish_callback| once the update completed or failed.
void UpdateAsync(const std::string& webapk_package,
const base::string16& short_name,
const std::unique_ptr<std::vector<uint8_t>> serialized_proto,
const FinishCallback& callback);
// the Google Play server to install the downloaded WebAPK.
// |update_request_path| is the path of the file with the update request.
// Calls |finish_callback| once the update completed or failed.
void UpdateAsync(const base::FilePath& update_request_path,
const FinishCallback& finish_callback);
// Called with the contents of the update request file.
void OnReadUpdateRequest(std::unique_ptr<std::string> update_request);
// net::URLFetcherDelegate:
void OnURLFetchComplete(const net::URLFetcher* source) override;
......@@ -150,7 +160,7 @@ class WebApkInstaller : public net::URLFetcherDelegate {
// Sends a request to WebAPK server to create/update WebAPK. During a
// successful request the WebAPK server responds with a token to send to
// Google Play.
void SendRequest(std::unique_ptr<std::vector<uint8_t>> serialized_proto);
void SendRequest(std::unique_ptr<std::string> serialized_proto);
net::URLRequestContextGetter* request_context_getter_;
......@@ -172,7 +182,6 @@ class WebApkInstaller : public net::URLFetcherDelegate {
SkBitmap install_primary_icon_;
SkBitmap install_badge_icon_;
GURL start_url_;
base::string16 short_name_;
// WebAPK server URL.
......
......@@ -13,6 +13,7 @@
#include "base/callback_forward.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
......@@ -111,20 +112,12 @@ class WebApkInstallerRunner {
run_loop.Run();
}
void RunUpdateWebApk(const std::string& serialized_proto) {
void RunUpdateWebApk(const base::FilePath& update_request_path) {
base::RunLoop run_loop;
on_completed_callback_ = run_loop.QuitClosure();
std::map<std::string, std::string> icon_url_to_murmur2_hash{
{best_primary_icon_url_.spec(), "0"},
{best_badge_icon_url_.spec(), "0"}};
std::unique_ptr<std::vector<uint8_t>> serialized_proto_vector =
base::MakeUnique<std::vector<uint8_t>>(serialized_proto.begin(),
serialized_proto.end());
WebApkInstaller::UpdateAsyncForTesting(
CreateWebApkInstaller(), kDownloadedWebApkPackageName,
base::string16() /* short_name */, std::move(serialized_proto_vector),
CreateWebApkInstaller(), update_request_path,
base::Bind(&WebApkInstallerRunner::OnCompleted,
base::Unretained(this)));
......@@ -163,6 +156,30 @@ class WebApkInstallerRunner {
DISALLOW_COPY_AND_ASSIGN(WebApkInstallerRunner);
};
// Helper class for calling WebApkInstaller::StoreUpdateRequestToFile()
// synchronously.
class UpdateRequestStorer {
public:
UpdateRequestStorer() {}
void StoreSync(const base::FilePath& update_request_path) {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
WebApkInstaller::StoreUpdateRequestToFile(
update_request_path, ShortcutInfo((GURL())), SkBitmap(), SkBitmap(), "",
"", std::map<std::string, std::string>(), false,
base::Bind(&UpdateRequestStorer::OnComplete, base::Unretained(this)));
run_loop.Run();
}
private:
void OnComplete(bool success) { quit_closure_.Run(); }
base::Closure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(UpdateRequestStorer);
};
// Builds a webapk::WebApkResponse with |token| as the token from the WebAPK
// server.
std::unique_ptr<net::test_server::HttpResponse> BuildValidWebApkResponse(
......@@ -213,11 +230,9 @@ class BuildProtoRunner {
private:
// Called when the |webapk_request_| is populated.
void OnBuiltWebApkProto(
std::unique_ptr<std::vector<uint8_t>> serialized_proto) {
void OnBuiltWebApkProto(std::unique_ptr<std::string> serialized_proto) {
webapk_request_ = base::MakeUnique<webapk::WebApk>();
webapk_request_->ParseFromArray(serialized_proto->data(),
serialized_proto->size());
webapk_request_->ParseFromString(*serialized_proto);
on_completed_callback_.Run();
}
......@@ -230,6 +245,22 @@ class BuildProtoRunner {
DISALLOW_COPY_AND_ASSIGN(BuildProtoRunner);
};
class ScopedTempFile {
public:
ScopedTempFile() { CHECK(base::CreateTemporaryFile(&file_path_)); }
~ScopedTempFile() {
base::DeleteFile(file_path_, false);
}
const base::FilePath& GetFilePath() { return file_path_; }
private:
base::FilePath file_path_;
DISALLOW_COPY_AND_ASSIGN(ScopedTempFile);
};
} // anonymous namespace
class WebApkInstallerTest : public ::testing::Test {
......@@ -385,33 +416,76 @@ TEST_F(WebApkInstallerTest, UnparsableCreateWebApkResponse) {
// Test update succeeding.
TEST_F(WebApkInstallerTest, UpdateSuccess) {
ScopedTempFile scoped_file;
base::FilePath update_request_path = scoped_file.GetFilePath();
UpdateRequestStorer().StoreSync(update_request_path);
ASSERT_TRUE(base::PathExists(update_request_path));
std::unique_ptr<WebApkInstallerRunner> runner = CreateWebApkInstallerRunner();
runner->RunUpdateWebApk("non-empty");
runner->RunUpdateWebApk(update_request_path);
EXPECT_EQ(WebApkInstallResult::SUCCESS, runner->result());
}
// Test that an update suceeds if the WebAPK server returns a HTTP response with
// an empty token. The WebAPK server sends an empty download URL when:
// an empty token. The WebAPK server sends an empty token when:
// - The server is unable to update the WebAPK in the way that the client
// requested.
// AND
// - The most up to date version of the WebAPK on the server is identical to the
// one installed on the client.
TEST_F(WebApkInstallerTest, UpdateSuccessWithEmptyDownloadUrlInResponse) {
TEST_F(WebApkInstallerTest, UpdateSuccessWithEmptyTokenInResponse) {
SetWebApkResponseBuilder(base::Bind(&BuildValidWebApkResponse, ""));
ScopedTempFile scoped_file;
base::FilePath update_request_path = scoped_file.GetFilePath();
UpdateRequestStorer().StoreSync(update_request_path);
ASSERT_TRUE(base::PathExists(update_request_path));
std::unique_ptr<WebApkInstallerRunner> runner = CreateWebApkInstallerRunner();
runner->RunUpdateWebApk("non-empty");
runner->RunUpdateWebApk(update_request_path);
EXPECT_EQ(WebApkInstallResult::SUCCESS, runner->result());
}
// Test that an update fails if an empty proto is passed to UpdateAsync().
TEST_F(WebApkInstallerTest, UpdateFailsEmptyProto) {
// Test that an update fails if the "update request path" points to an update
// file with the incorrect format.
TEST_F(WebApkInstallerTest, UpdateFailsUpdateRequestWrongFormat) {
ScopedTempFile scoped_file;
base::FilePath update_request_path = scoped_file.GetFilePath();
base::WriteFile(update_request_path, "😀", 1);
std::unique_ptr<WebApkInstallerRunner> runner = CreateWebApkInstallerRunner();
runner->RunUpdateWebApk("");
runner->RunUpdateWebApk(update_request_path);
EXPECT_EQ(WebApkInstallResult::FAILURE, runner->result());
}
// Test that an update fails if the "update request path" points to a
// non-existing file.
TEST_F(WebApkInstallerTest, UpdateFailsUpdateRequestFileDoesNotExist) {
base::FilePath update_request_path;
{
ScopedTempFile scoped_file;
update_request_path = scoped_file.GetFilePath();
}
ASSERT_FALSE(base::PathExists(update_request_path));
std::unique_ptr<WebApkInstallerRunner> runner = CreateWebApkInstallerRunner();
runner->RunUpdateWebApk(update_request_path);
EXPECT_EQ(WebApkInstallResult::FAILURE, runner->result());
}
// Test that StoreUpdateRequestToFile() creates directories if needed when
// writing to the passed in |update_file_path|.
TEST_F(WebApkInstallerTest, StoreUpdateRequestToFileCreatesDirectories) {
base::FilePath outer_file_path;
ASSERT_TRUE(CreateNewTempDirectory("", &outer_file_path));
base::FilePath update_request_path =
outer_file_path.Append("deep").Append("deeper");
UpdateRequestStorer().StoreSync(update_request_path);
EXPECT_TRUE(base::PathExists(update_request_path));
// Clean up
base::DeleteFile(outer_file_path, true /* recursive */);
}
// When there is no Web Manifest available for a site, an empty
// |best_primary_icon_url| and an empty |best_badge_icon_url| is used to build a
// WebApk update request. Tests the request can be built properly.
......
......@@ -11,6 +11,7 @@
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string16.h"
#include "base/threading/thread_task_runner_handle.h"
......@@ -31,10 +32,10 @@ using base::android::ScopedJavaGlobalRef;
namespace {
// Called with the serialized proto to send to the WebAPK server.
void OnBuiltProto(const JavaRef<jobject>& java_callback,
std::unique_ptr<std::vector<uint8_t>> proto) {
base::android::RunCallbackAndroid(java_callback, *proto);
// Called after saving the update request proto either succeeds or fails.
void OnStoredUpdateRequest(const JavaRef<jobject>& java_callback,
bool success) {
base::android::RunCallbackAndroid(java_callback, success);
}
// Called after the update either succeeds or fails.
......@@ -50,9 +51,10 @@ void OnUpdated(const JavaRef<jobject>& java_callback,
} // anonymous namespace
// static JNI method.
static void BuildUpdateWebApkProto(
static void StoreWebApkUpdateRequestToFile(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& java_update_request_path,
const JavaParamRef<jstring>& java_start_url,
const JavaParamRef<jstring>& java_scope,
const JavaParamRef<jstring>& java_name,
......@@ -74,6 +76,9 @@ static void BuildUpdateWebApkProto(
const JavaParamRef<jobject>& java_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string update_request_path =
ConvertJavaStringToUTF8(env, java_update_request_path);
ShortcutInfo info(GURL(ConvertJavaStringToUTF8(env, java_start_url)));
info.scope = GURL(ConvertJavaStringToUTF8(env, java_scope));
info.name = ConvertJavaStringToUTF16(env, java_name);
......@@ -116,20 +121,20 @@ static void BuildUpdateWebApkProto(
std::string webapk_package;
ConvertJavaStringToUTF8(env, java_webapk_package, &webapk_package);
WebApkInstaller::BuildProto(
info, primary_icon, badge_icon, webapk_package,
std::to_string(java_webapk_version), icon_url_to_murmur2_hash,
java_is_manifest_stale,
base::Bind(&OnBuiltProto, ScopedJavaGlobalRef<jobject>(java_callback)));
WebApkInstaller::StoreUpdateRequestToFile(
base::FilePath(update_request_path), info, primary_icon, badge_icon,
webapk_package, std::to_string(java_webapk_version),
icon_url_to_murmur2_hash, java_is_manifest_stale,
base::Bind(&OnStoredUpdateRequest,
ScopedJavaGlobalRef<jobject>(java_callback)));
}
// static JNI method.
static void UpdateWebApk(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& java_webapk_package,
const JavaParamRef<jstring>& java_short_name,
const JavaParamRef<jbyteArray>& java_serialized_proto,
const JavaParamRef<jobject>& java_callback) {
static void UpdateWebApkFromFile(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& java_update_request_path,
const JavaParamRef<jobject>& java_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ScopedJavaGlobalRef<jobject> callback_ref(java_callback);
......@@ -143,14 +148,9 @@ static void UpdateWebApk(JNIEnv* env,
return;
}
std::string webapk_package =
ConvertJavaStringToUTF8(env, java_webapk_package);
base::string16 short_name = ConvertJavaStringToUTF16(env, java_short_name);
std::unique_ptr<std::vector<uint8_t>> serialized_proto =
base::MakeUnique<std::vector<uint8_t>>();
JavaByteArrayToByteVector(env, java_serialized_proto, serialized_proto.get());
std::string update_request_path =
ConvertJavaStringToUTF8(env, java_update_request_path);
WebApkInstallService::Get(profile)->UpdateAsync(
webapk_package, short_name, std::move(serialized_proto),
base::FilePath(update_request_path),
base::Bind(&OnUpdated, callback_ref));
}
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