Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/android/webapk/webapk_installer.h" | 5 #include "chrome/browser/android/webapk/webapk_installer.h" |
| 6 | 6 |
| 7 #include "base/android/build_info.h" | 7 #include "base/android/build_info.h" |
| 8 #include "base/android/jni_android.h" | 8 #include "base/android/jni_android.h" |
| 9 #include "base/android/jni_string.h" | 9 #include "base/android/jni_string.h" |
| 10 #include "base/android/path_utils.h" | 10 #include "base/android/path_utils.h" |
| 11 #include "base/bind.h" | 11 #include "base/bind.h" |
| 12 #include "base/command_line.h" | 12 #include "base/command_line.h" |
| 13 #include "base/files/file_path.h" | 13 #include "base/files/file_path.h" |
| 14 #include "base/files/file_util.h" | 14 #include "base/files/file_util.h" |
| 15 #include "base/memory/ref_counted.h" | 15 #include "base/memory/ref_counted.h" |
| 16 #include "base/strings/string_number_conversions.h" | |
| 17 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
| 18 #include "base/strings/stringprintf.h" | 17 #include "base/strings/stringprintf.h" |
| 19 #include "base/strings/utf_string_conversions.h" | 18 #include "base/strings/utf_string_conversions.h" |
| 20 #include "base/task_runner_util.h" | 19 #include "base/task_runner_util.h" |
| 21 #include "base/threading/sequenced_worker_pool.h" | 20 #include "base/threading/sequenced_worker_pool.h" |
| 22 #include "chrome/browser/android/shortcut_helper.h" | 21 #include "chrome/browser/android/shortcut_helper.h" |
| 23 #include "chrome/browser/android/webapk/webapk.pb.h" | 22 #include "chrome/browser/android/webapk/webapk.pb.h" |
| 23 #include "chrome/browser/android/webapk/webapk_icon_hasher.h" | |
| 24 #include "chrome/browser/profiles/profile.h" | 24 #include "chrome/browser/profiles/profile.h" |
| 25 #include "chrome/common/chrome_switches.h" | 25 #include "chrome/common/chrome_switches.h" |
| 26 #include "components/version_info/version_info.h" | 26 #include "components/version_info/version_info.h" |
| 27 #include "content/public/browser/browser_thread.h" | 27 #include "content/public/browser/browser_thread.h" |
| 28 #include "content/public/common/manifest_util.h" | |
| 29 #include "jni/WebApkInstaller_jni.h" | 28 #include "jni/WebApkInstaller_jni.h" |
| 30 #include "net/http/http_status_code.h" | 29 #include "net/http/http_status_code.h" |
| 31 #include "net/url_request/url_fetcher.h" | 30 #include "net/url_request/url_fetcher.h" |
| 32 #include "third_party/smhasher/src/MurmurHash2.h" | |
| 33 #include "ui/gfx/codec/png_codec.h" | 31 #include "ui/gfx/codec/png_codec.h" |
| 34 #include "url/gurl.h" | 32 #include "url/gurl.h" |
| 35 | 33 |
| 36 namespace { | 34 namespace { |
| 37 | 35 |
| 38 // The default WebAPK server URL. | 36 // The default WebAPK server URL. |
| 39 const char kDefaultWebApkServerUrl[] = | 37 const char kDefaultWebApkServerUrl[] = |
| 40 "https://webapk.googleapis.com/v1alpha/webApks?alt=proto"; | 38 "https://webapk.googleapis.com/v1alpha/webApks?alt=proto"; |
| 41 | 39 |
| 42 // The MIME type of the POST data sent to the server. | 40 // The MIME type of the POST data sent to the server. |
| 43 const char kProtoMimeType[] = "application/x-protobuf"; | 41 const char kProtoMimeType[] = "application/x-protobuf"; |
| 44 | 42 |
| 45 // The seed to use the murmur2 hash of the app icon. | |
| 46 const uint32_t kMurmur2HashSeed = 0; | |
| 47 | |
| 48 // The default number of milliseconds to wait for the WebAPK download URL from | 43 // The default number of milliseconds to wait for the WebAPK download URL from |
| 49 // the WebAPK server. | 44 // the WebAPK server. |
| 50 const int kWebApkDownloadUrlTimeoutMs = 60000; | 45 const int kWebApkDownloadUrlTimeoutMs = 60000; |
| 51 | 46 |
| 52 // The default number of milliseconds to wait for the WebAPK download to | 47 // The default number of milliseconds to wait for the WebAPK download to |
| 53 // complete. | 48 // complete. |
| 54 const int kDownloadTimeoutMs = 60000; | 49 const int kDownloadTimeoutMs = 60000; |
| 55 | 50 |
| 56 // Returns the scope from |info| if it is specified. Otherwise, returns the | 51 // Returns the scope from |info| if it is specified. Otherwise, returns the |
| 57 // default scope. | 52 // default scope. |
| 58 GURL GetScope(const ShortcutInfo& info) { | 53 GURL GetScope(const ShortcutInfo& info) { |
| 59 return (info.scope.is_valid()) | 54 return (info.scope.is_valid()) |
| 60 ? info.scope | 55 ? info.scope |
| 61 : ShortcutHelper::GetScopeFromURL(info.url); | 56 : ShortcutHelper::GetScopeFromURL(info.url); |
| 62 } | 57 } |
| 63 | 58 |
| 64 // Computes a murmur2 hash of |bitmap|'s PNG encoded bytes. | |
| 65 std::string ComputeBitmapHash(const SkBitmap& bitmap) { | |
| 66 std::vector<unsigned char> png_bytes; | |
| 67 gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_bytes); | |
| 68 uint64_t hash = | |
| 69 MurmurHash64B(&png_bytes.front(), png_bytes.size(), kMurmur2HashSeed); | |
| 70 return base::Uint64ToString(hash); | |
| 71 } | |
| 72 | |
| 73 // Converts a color from the format specified in content::Manifest to a CSS | 59 // Converts a color from the format specified in content::Manifest to a CSS |
| 74 // string. | 60 // string. |
| 75 std::string ColorToString(int64_t color) { | 61 std::string ColorToString(int64_t color) { |
| 76 if (color == content::Manifest::kInvalidOrMissingColor) | 62 if (color == content::Manifest::kInvalidOrMissingColor) |
| 77 return ""; | 63 return ""; |
| 78 | 64 |
| 79 SkColor sk_color = reinterpret_cast<uint32_t&>(color); | 65 SkColor sk_color = reinterpret_cast<uint32_t&>(color); |
| 80 int r = SkColorGetR(sk_color); | 66 int r = SkColorGetR(sk_color); |
| 81 int g = SkColorGetG(sk_color); | 67 int g = SkColorGetG(sk_color); |
| 82 int b = SkColorGetB(sk_color); | 68 int b = SkColorGetB(sk_color); |
| 83 double a = SkColorGetA(sk_color) / 255.0; | 69 double a = SkColorGetA(sk_color) / 255.0; |
| 84 return base::StringPrintf("rgba(%d,%d,%d,%.2f)", r, g, b, a); | 70 return base::StringPrintf("rgba(%d,%d,%d,%.2f)", r, g, b, a); |
| 85 } | 71 } |
| 86 | 72 |
| 73 // Populates webapk::WebApk and returns it. | |
| 74 // Must be called on a worker thread because it encodes an SkBitmap. | |
| 75 std::unique_ptr<webapk::WebApk> BuildWebApkProtoInBackground( | |
| 76 const ShortcutInfo& shortcut_info, | |
| 77 const SkBitmap& shortcut_icon, | |
| 78 const std::string& shortcut_icon_murmur2_hash) { | |
| 79 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 80 | |
| 81 std::unique_ptr<webapk::WebApk> webapk(new webapk::WebApk); | |
| 82 webapk->set_manifest_url(shortcut_info.manifest_url.spec()); | |
| 83 webapk->set_requester_application_package( | |
| 84 base::android::BuildInfo::GetInstance()->package_name()); | |
| 85 webapk->set_requester_application_version(version_info::GetVersionNumber()); | |
| 86 | |
| 87 webapk::WebAppManifest* web_app_manifest = webapk->mutable_manifest(); | |
| 88 web_app_manifest->set_name(base::UTF16ToUTF8(shortcut_info.name)); | |
| 89 web_app_manifest->set_short_name( | |
| 90 base::UTF16ToUTF8(shortcut_info.short_name)); | |
| 91 web_app_manifest->set_start_url(shortcut_info.url.spec()); | |
| 92 web_app_manifest->set_orientation( | |
| 93 content::WebScreenOrientationLockTypeToString( | |
| 94 shortcut_info.orientation)); | |
| 95 web_app_manifest->set_display_mode( | |
| 96 content::WebDisplayModeToString(shortcut_info.display)); | |
| 97 web_app_manifest->set_background_color( | |
| 98 ColorToString(shortcut_info.background_color)); | |
| 99 web_app_manifest->set_theme_color(ColorToString(shortcut_info.theme_color)); | |
| 100 | |
| 101 std::string* scope = web_app_manifest->add_scopes(); | |
| 102 scope->assign(GetScope(shortcut_info).spec()); | |
| 103 webapk::Image* image = web_app_manifest->add_icons(); | |
| 104 image->set_src(shortcut_info.icon_url.spec()); | |
| 105 image->set_hash(shortcut_icon_murmur2_hash); | |
| 106 std::vector<unsigned char> png_bytes; | |
| 107 gfx::PNGCodec::EncodeBGRASkBitmap(shortcut_icon, false, &png_bytes); | |
| 108 image->set_image_data(&png_bytes.front(), png_bytes.size()); | |
| 109 | |
| 110 return webapk; | |
| 111 } | |
| 112 | |
| 87 // Returns task runner for running background tasks. | 113 // Returns task runner for running background tasks. |
| 88 scoped_refptr<base::TaskRunner> GetBackgroundTaskRunner() { | 114 scoped_refptr<base::TaskRunner> GetBackgroundTaskRunner() { |
| 89 return content::BrowserThread::GetBlockingPool() | 115 return content::BrowserThread::GetBlockingPool() |
| 90 ->GetTaskRunnerWithShutdownBehavior( | 116 ->GetTaskRunnerWithShutdownBehavior( |
| 91 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); | 117 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); |
| 92 } | 118 } |
| 93 | 119 |
| 94 } // anonymous namespace | 120 } // anonymous namespace |
| 95 | 121 |
| 96 WebApkInstaller::WebApkInstaller(const ShortcutInfo& shortcut_info, | 122 WebApkInstaller::WebApkInstaller(const ShortcutInfo& shortcut_info, |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 115 Profile::FromBrowserContext(browser_context)->GetRequestContext(), | 141 Profile::FromBrowserContext(browser_context)->GetRequestContext(), |
| 116 finish_callback); | 142 finish_callback); |
| 117 } | 143 } |
| 118 | 144 |
| 119 void WebApkInstaller::InstallAsyncWithURLRequestContextGetter( | 145 void WebApkInstaller::InstallAsyncWithURLRequestContextGetter( |
| 120 net::URLRequestContextGetter* request_context_getter, | 146 net::URLRequestContextGetter* request_context_getter, |
| 121 const FinishCallback& finish_callback) { | 147 const FinishCallback& finish_callback) { |
| 122 request_context_getter_ = request_context_getter; | 148 request_context_getter_ = request_context_getter; |
| 123 finish_callback_ = finish_callback; | 149 finish_callback_ = finish_callback; |
| 124 | 150 |
| 125 // base::Unretained() is safe because WebApkInstaller owns itself and does not | 151 if (shortcut_info_.icon_url.is_valid()) { |
| 126 // start the timeout timer till after SendCreateWebApkRequest() is called. | 152 // We need to take the hash of the bitmap at the icon URL prior to any |
| 127 scoped_refptr<base::TaskRunner> background_task_runner = | 153 // transformations being applied to the bitmap (such as encoding/decoding |
| 128 content::BrowserThread::GetBlockingPool() | 154 // the bitmap). The WebAPK server crawls the internet and generates WebAPKs |
|
Yaron
2016/08/12 21:24:05
it doesn't actually crawl the internet per-say and
| |
| 129 ->GetTaskRunnerWithShutdownBehavior( | 155 // for any Web Manifests that it finds. The icon hash is used to determine |
| 130 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); | 156 // whether the icon that the user sees matches the icon of a WebAPK that the |
| 131 base::PostTaskAndReplyWithResult( | 157 // WebAPK server generated while crawling the internet. |
| 132 background_task_runner.get(), FROM_HERE, | 158 // |
| 133 base::Bind(&WebApkInstaller::BuildWebApkProtoInBackground, | 159 // We redownload the icon in order to take the Murmur2 hash. The redownload |
| 134 base::Unretained(this)), | 160 // should be fast because the icon should be in the HTTP cache. |
|
Yaron
2016/08/12 21:24:05
have you tested that locally? is there a way to se
pkotwicz
2016/08/15 17:19:26
Yes, I have tested locally and the icon is loaded
| |
| 135 base::Bind(&WebApkInstaller::SendCreateWebApkRequest, | 161 DownloadAppIconAndComputeMurmur2Hash(); |
| 136 base::Unretained(this))); | 162 return; |
| 163 } | |
| 164 SendCreateWebApkRequest(); | |
| 137 } | 165 } |
| 138 | 166 |
| 139 void WebApkInstaller::SetTimeoutMs(int timeout_ms) { | 167 void WebApkInstaller::SetTimeoutMs(int timeout_ms) { |
| 140 webapk_download_url_timeout_ms_ = timeout_ms; | 168 webapk_download_url_timeout_ms_ = timeout_ms; |
| 141 download_timeout_ms_ = timeout_ms; | 169 download_timeout_ms_ = timeout_ms; |
| 142 } | 170 } |
| 143 | 171 |
| 144 bool WebApkInstaller::StartDownloadedWebApkInstall( | 172 bool WebApkInstaller::StartDownloadedWebApkInstall( |
| 145 JNIEnv* env, | 173 JNIEnv* env, |
| 146 const base::android::ScopedJavaLocalRef<jstring>& java_file_path, | 174 const base::android::ScopedJavaLocalRef<jstring>& java_file_path, |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 169 } | 197 } |
| 170 | 198 |
| 171 GURL signed_download_url(response->signed_download_url()); | 199 GURL signed_download_url(response->signed_download_url()); |
| 172 if (!signed_download_url.is_valid() || response->package_name().empty()) { | 200 if (!signed_download_url.is_valid() || response->package_name().empty()) { |
| 173 OnFailure(); | 201 OnFailure(); |
| 174 return; | 202 return; |
| 175 } | 203 } |
| 176 OnGotWebApkDownloadUrl(signed_download_url, response->package_name()); | 204 OnGotWebApkDownloadUrl(signed_download_url, response->package_name()); |
| 177 } | 205 } |
| 178 | 206 |
| 179 void WebApkInstaller::SendCreateWebApkRequest( | 207 void WebApkInstaller::DownloadAppIconAndComputeMurmur2Hash() { |
| 208 timer_.Start( | |
| 209 FROM_HERE, base::TimeDelta::FromMilliseconds(download_timeout_ms_), | |
| 210 base::Bind(&WebApkInstaller::OnTimeout, weak_ptr_factory_.GetWeakPtr())); | |
| 211 | |
| 212 icon_hasher_.reset(new WebApkIconHasher()); | |
| 213 icon_hasher_->DownloadAndGetMurmur2Hash( | |
| 214 request_context_getter_, shortcut_info_.icon_url, | |
| 215 base::Bind(&WebApkInstaller::OnGotIconMurmur2Hash, | |
| 216 weak_ptr_factory_.GetWeakPtr())); | |
| 217 } | |
| 218 | |
| 219 void WebApkInstaller::OnGotIconMurmur2Hash( | |
| 220 const std::string& icon_murmur2_hash) { | |
| 221 timer_.Stop(); | |
| 222 icon_hasher_.reset(); | |
| 223 | |
| 224 shortcut_icon_murmur2_hash_ = icon_murmur2_hash; | |
| 225 | |
| 226 // An empty hash indicates that |icon_hasher_| encountered an error. | |
| 227 if (icon_murmur2_hash.empty()) { | |
| 228 OnFailure(); | |
| 229 return; | |
| 230 } | |
| 231 | |
| 232 SendCreateWebApkRequest(); | |
| 233 } | |
| 234 | |
| 235 void WebApkInstaller::SendCreateWebApkRequest() { | |
| 236 base::PostTaskAndReplyWithResult( | |
| 237 GetBackgroundTaskRunner().get(), FROM_HERE, | |
| 238 base::Bind(&BuildWebApkProtoInBackground, shortcut_info_, shortcut_icon_, | |
| 239 shortcut_icon_murmur2_hash_), | |
| 240 base::Bind(&WebApkInstaller::SendCreateWebApkRequestWithProto, | |
| 241 weak_ptr_factory_.GetWeakPtr())); | |
| 242 } | |
| 243 | |
| 244 void WebApkInstaller::SendCreateWebApkRequestWithProto( | |
| 180 std::unique_ptr<webapk::WebApk> webapk_proto) { | 245 std::unique_ptr<webapk::WebApk> webapk_proto) { |
| 181 timer_.Start( | 246 timer_.Start( |
| 182 FROM_HERE, | 247 FROM_HERE, |
| 183 base::TimeDelta::FromMilliseconds(webapk_download_url_timeout_ms_), | 248 base::TimeDelta::FromMilliseconds(webapk_download_url_timeout_ms_), |
| 184 base::Bind(&WebApkInstaller::OnTimeout, weak_ptr_factory_.GetWeakPtr())); | 249 base::Bind(&WebApkInstaller::OnTimeout, weak_ptr_factory_.GetWeakPtr())); |
| 185 | 250 |
| 186 url_fetcher_ = | 251 url_fetcher_ = |
| 187 net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this); | 252 net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this); |
| 188 url_fetcher_->SetRequestContext(request_context_getter_); | 253 url_fetcher_->SetRequestContext(request_context_getter_); |
| 189 std::string serialized_request; | 254 std::string serialized_request; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 247 base::android::ScopedJavaLocalRef<jstring> java_package_name = | 312 base::android::ScopedJavaLocalRef<jstring> java_package_name = |
| 248 base::android::ConvertUTF8ToJavaString(env, package_name); | 313 base::android::ConvertUTF8ToJavaString(env, package_name); |
| 249 bool success = | 314 bool success = |
| 250 StartDownloadedWebApkInstall(env, java_file_path, java_package_name); | 315 StartDownloadedWebApkInstall(env, java_file_path, java_package_name); |
| 251 if (success) | 316 if (success) |
| 252 OnSuccess(); | 317 OnSuccess(); |
| 253 else | 318 else |
| 254 OnFailure(); | 319 OnFailure(); |
| 255 } | 320 } |
| 256 | 321 |
| 257 std::unique_ptr<webapk::WebApk> | |
| 258 WebApkInstaller::BuildWebApkProtoInBackground() { | |
| 259 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 260 | |
| 261 std::unique_ptr<webapk::WebApk> webapk(new webapk::WebApk); | |
| 262 webapk->set_manifest_url(shortcut_info_.manifest_url.spec()); | |
| 263 webapk->set_requester_application_package( | |
| 264 base::android::BuildInfo::GetInstance()->package_name()); | |
| 265 webapk->set_requester_application_version(version_info::GetVersionNumber()); | |
| 266 | |
| 267 webapk::WebAppManifest* web_app_manifest = webapk->mutable_manifest(); | |
| 268 web_app_manifest->set_name(base::UTF16ToUTF8(shortcut_info_.name)); | |
| 269 web_app_manifest->set_short_name( | |
| 270 base::UTF16ToUTF8(shortcut_info_.short_name)); | |
| 271 web_app_manifest->set_start_url(shortcut_info_.url.spec()); | |
| 272 web_app_manifest->set_orientation( | |
| 273 content::WebScreenOrientationLockTypeToString( | |
| 274 shortcut_info_.orientation)); | |
| 275 web_app_manifest->set_display_mode( | |
| 276 content::WebDisplayModeToString(shortcut_info_.display)); | |
| 277 web_app_manifest->set_background_color( | |
| 278 ColorToString(shortcut_info_.background_color)); | |
| 279 web_app_manifest->set_theme_color(ColorToString(shortcut_info_.theme_color)); | |
| 280 | |
| 281 std::string* scope = web_app_manifest->add_scopes(); | |
| 282 scope->assign(GetScope(shortcut_info_).spec()); | |
| 283 webapk::Image* image = web_app_manifest->add_icons(); | |
| 284 image->set_src(shortcut_info_.icon_url.spec()); | |
| 285 // TODO(pkotwicz): Get Murmur2 hash of untransformed icon's bytes (with no | |
| 286 // encoding/decoding). | |
| 287 image->set_hash(ComputeBitmapHash(shortcut_icon_)); | |
| 288 std::vector<unsigned char> png_bytes; | |
| 289 gfx::PNGCodec::EncodeBGRASkBitmap(shortcut_icon_, false, &png_bytes); | |
| 290 image->set_image_data(&png_bytes.front(), png_bytes.size()); | |
| 291 | |
| 292 return webapk; | |
| 293 } | |
| 294 | |
| 295 void WebApkInstaller::OnTimeout() { | 322 void WebApkInstaller::OnTimeout() { |
| 296 OnFailure(); | 323 OnFailure(); |
| 297 } | 324 } |
| 298 | 325 |
| 299 void WebApkInstaller::OnSuccess() { | 326 void WebApkInstaller::OnSuccess() { |
| 300 finish_callback_.Run(true); | 327 FinishCallback callback = finish_callback_; |
| 301 delete this; | 328 delete this; |
| 329 callback.Run(true); | |
| 302 } | 330 } |
| 303 | 331 |
| 304 void WebApkInstaller::OnFailure() { | 332 void WebApkInstaller::OnFailure() { |
| 305 finish_callback_.Run(false); | 333 FinishCallback callback = finish_callback_; |
| 306 delete this; | 334 delete this; |
| 335 callback.Run(false); | |
| 307 } | 336 } |
| OLD | NEW |