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