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 |