blob: 5e28acdec90c9423677c17e59fc2ac5bd71d7c15 [file] [log] [blame]
[email protected]51208252013-08-19 21:05:301// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/android/shortcut_helper.h"
6
7#include <jni.h>
gonzalon87192d772017-02-07 22:31:108#include <utility>
[email protected]51208252013-08-19 21:05:309
10#include "base/android/jni_android.h"
lalitm45a03c72015-09-16 13:00:4311#include "base/android/jni_array.h"
[email protected]51208252013-08-19 21:05:3012#include "base/android/jni_string.h"
dominickn6509a4de2016-04-06 08:29:0613#include "base/bind.h"
14#include "base/callback.h"
pkotwiczd0d459e2017-03-01 22:55:1015#include "base/guid.h"
[email protected]51208252013-08-19 21:05:3016#include "base/strings/string16.h"
mlamouric679bbf2014-09-24 21:24:4917#include "base/strings/utf_string_conversions.h"
tzika1fd66f2017-02-15 07:58:4518#include "base/threading/sequenced_worker_pool.h"
hanxi563f3532016-08-19 20:09:0119#include "chrome/browser/android/webapk/chrome_webapk_host.h"
hanxi070d10a2017-01-09 15:56:5020#include "chrome/browser/android/webapk/webapk_install_service.h"
pkotwiczf4dddb32017-02-25 01:21:5521#include "chrome/browser/android/webapk/webapk_metrics.h"
lalitmd93c2ed2015-09-04 16:22:1222#include "chrome/browser/manifest/manifest_icon_downloader.h"
pkotwicz206673142016-07-19 19:13:3023#include "chrome/common/chrome_switches.h"
dfalcantaraaec56da2015-05-06 03:33:5624#include "content/public/browser/browser_thread.h"
[email protected]51208252013-08-19 21:05:3025#include "content/public/browser/web_contents.h"
[email protected]51208252013-08-19 21:05:3026#include "jni/ShortcutHelper_jni.h"
27#include "ui/gfx/android/java_bitmap.h"
mlamouribc6e8792015-10-22 20:41:1328#include "ui/gfx/color_analysis.h"
[email protected]51208252013-08-19 21:05:3029#include "url/gurl.h"
30
torne86560112016-08-04 15:59:0431using base::android::JavaParamRef;
32using base::android::ScopedJavaLocalRef;
mlamouric679bbf2014-09-24 21:24:4933using content::Manifest;
34
lalitm45a03c72015-09-16 13:00:4335namespace {
36
zpenge33ba852017-02-01 20:54:4237int g_ideal_homescreen_icon_size = -1;
38int g_minimum_homescreen_icon_size = -1;
39int g_ideal_splash_image_size = -1;
40int g_minimum_splash_image_size = -1;
41int g_ideal_badge_icon_size = -1;
lalitm45a03c72015-09-16 13:00:4342
zpenge33ba852017-02-01 20:54:4243int g_default_rgb_icon_value = 145;
mlamouribc6e8792015-10-22 20:41:1344
lalitm45a03c72015-09-16 13:00:4345// Retrieves and caches the ideal and minimum sizes of the Home screen icon
46// and the splash screen image.
47void GetHomescreenIconAndSplashImageSizes() {
48 JNIEnv* env = base::android::AttachCurrentThread();
49 ScopedJavaLocalRef<jintArray> java_size_array =
pkotwicz45fc42b62016-06-07 00:07:1050 Java_ShortcutHelper_getHomeScreenIconAndSplashImageSizes(env);
lalitm45a03c72015-09-16 13:00:4351 std::vector<int> sizes;
zpenga7856eef2017-02-07 11:42:4452 base::android::JavaIntArrayToIntVector(env, java_size_array.obj(), &sizes);
lalitm45a03c72015-09-16 13:00:4353
54 // Check that the size returned is what is expected.
zpenge33ba852017-02-01 20:54:4255 DCHECK(sizes.size() == 5);
lalitm45a03c72015-09-16 13:00:4356
57 // This ordering must be kept up to date with the Java ShortcutHelper.
zpenge33ba852017-02-01 20:54:4258 g_ideal_homescreen_icon_size = sizes[0];
59 g_minimum_homescreen_icon_size = sizes[1];
60 g_ideal_splash_image_size = sizes[2];
61 g_minimum_splash_image_size = sizes[3];
62 g_ideal_badge_icon_size = sizes[4];
lalitm45a03c72015-09-16 13:00:4363
64 // Try to ensure that the data returned is sane.
zpenge33ba852017-02-01 20:54:4265 DCHECK(g_minimum_homescreen_icon_size <= g_ideal_homescreen_icon_size);
66 DCHECK(g_minimum_splash_image_size <= g_ideal_splash_image_size);
lalitm45a03c72015-09-16 13:00:4367}
68
pkotwicz97478712017-03-01 03:39:5469// Adds a shortcut which opens in a fullscreen window to the launcher.
70// |splash_image_callback| will be invoked once the Java-side operation has
71// completed. This is necessary as Java will asynchronously create and
72// populate a WebappDataStorage object for standalone-capable sites. This must
73// exist before the splash image can be stored.
74void AddWebappWithSkBitmap(const ShortcutInfo& info,
75 const std::string& webapp_id,
76 const SkBitmap& icon_bitmap,
77 const base::Closure& splash_image_callback) {
[email protected]51208252013-08-19 21:05:3078 // Send the data to the Java side to create the shortcut.
79 JNIEnv* env = base::android::AttachCurrentThread();
lalitmd93c2ed2015-09-04 16:22:1280 ScopedJavaLocalRef<jstring> java_webapp_id =
81 base::android::ConvertUTF8ToJavaString(env, webapp_id);
[email protected]51208252013-08-19 21:05:3082 ScopedJavaLocalRef<jstring> java_url =
dfalcantara16e84de2015-02-03 22:07:4083 base::android::ConvertUTF8ToJavaString(env, info.url.spec());
pkotwicz6bdfbe1b2016-07-08 00:26:4384 ScopedJavaLocalRef<jstring> java_scope_url =
85 base::android::ConvertUTF8ToJavaString(env, info.scope.spec());
lalitmf3ee51852015-07-21 18:13:1186 ScopedJavaLocalRef<jstring> java_user_title =
87 base::android::ConvertUTF16ToJavaString(env, info.user_title);
88 ScopedJavaLocalRef<jstring> java_name =
89 base::android::ConvertUTF16ToJavaString(env, info.name);
90 ScopedJavaLocalRef<jstring> java_short_name =
91 base::android::ConvertUTF16ToJavaString(env, info.short_name);
zpenga7856eef2017-02-07 11:42:4492 ScopedJavaLocalRef<jstring> java_best_primary_icon_url =
93 base::android::ConvertUTF8ToJavaString(env,
94 info.best_primary_icon_url.spec());
[email protected]51208252013-08-19 21:05:3095 ScopedJavaLocalRef<jobject> java_bitmap;
mlamouric679bbf2014-09-24 21:24:4996 if (icon_bitmap.getSize())
97 java_bitmap = gfx::ConvertToJavaBitmap(&icon_bitmap);
[email protected]51208252013-08-19 21:05:3098
pkotwicz206673142016-07-19 19:13:3099 // The callback will need to be run after shortcut creation completes in order
100 // to download the splash image and save it to the WebappDataStorage. Create a
101 // copy of the callback here and send the pointer to Java, which will send it
102 // back once the asynchronous shortcut creation process finishes.
103 uintptr_t callback_pointer =
104 reinterpret_cast<uintptr_t>(new base::Closure(splash_image_callback));
dominickn6509a4de2016-04-06 08:29:06105
zpenga7856eef2017-02-07 11:42:44106 Java_ShortcutHelper_addWebapp(
107 env, java_webapp_id, java_url, java_scope_url, java_user_title, java_name,
108 java_short_name, java_best_primary_icon_url, java_bitmap, info.display,
109 info.orientation, info.source, info.theme_color, info.background_color,
110 callback_pointer);
[email protected]51208252013-08-19 21:05:30111}
benwells840ae902015-02-17 21:13:28112
pkotwicz97478712017-03-01 03:39:54113// Adds a shortcut which opens in a browser tab to the launcher.
114void AddShortcutWithSkBitmap(const ShortcutInfo& info,
115 const std::string& id,
116 const SkBitmap& icon_bitmap) {
pkotwicz206673142016-07-19 19:13:30117 JNIEnv* env = base::android::AttachCurrentThread();
martiw12166f52017-02-20 03:05:45118 ScopedJavaLocalRef<jstring> java_id =
119 base::android::ConvertUTF8ToJavaString(env, id);
pkotwicz206673142016-07-19 19:13:30120 ScopedJavaLocalRef<jstring> java_url =
121 base::android::ConvertUTF8ToJavaString(env, info.url.spec());
122 ScopedJavaLocalRef<jstring> java_user_title =
123 base::android::ConvertUTF16ToJavaString(env, info.user_title);
124 ScopedJavaLocalRef<jobject> java_bitmap;
125 if (icon_bitmap.getSize())
126 java_bitmap = gfx::ConvertToJavaBitmap(&icon_bitmap);
127
martiw12166f52017-02-20 03:05:45128 Java_ShortcutHelper_addShortcut(env, java_id, java_url, java_user_title,
129 java_bitmap, info.source);
pkotwicz206673142016-07-19 19:13:30130}
131
pkotwicz97478712017-03-01 03:39:54132} // anonymous namespace
133
134// static
135void ShortcutHelper::AddToLauncherWithSkBitmap(
pkotwiczd0d459e2017-03-01 22:55:10136 content::WebContents* web_contents,
pkotwicz97478712017-03-01 03:39:54137 const ShortcutInfo& info,
pkotwiczd0d459e2017-03-01 22:55:10138 const SkBitmap& icon_bitmap) {
139 std::string webapp_id = base::GenerateGUID();
pkotwicz97478712017-03-01 03:39:54140 if (info.display == blink::WebDisplayModeStandalone ||
141 info.display == blink::WebDisplayModeFullscreen) {
pkotwiczd0d459e2017-03-01 22:55:10142 AddWebappWithSkBitmap(
143 info, webapp_id, icon_bitmap,
144 base::Bind(&ShortcutHelper::FetchSplashScreenImage, web_contents,
145 info.splash_image_url, info.ideal_splash_image_size_in_px,
146 info.minimum_splash_image_size_in_px, webapp_id));
pkotwicz97478712017-03-01 03:39:54147 GooglePlayInstallState state =
148 ChromeWebApkHost::GetGooglePlayInstallState();
149 if (state != GooglePlayInstallState::SUPPORTED)
150 webapk::TrackGooglePlayInstallState(state);
151 return;
152 }
153 AddShortcutWithSkBitmap(info, webapp_id, icon_bitmap);
154}
155
156// static
157void ShortcutHelper::InstallWebApkWithSkBitmap(
pkotwiczd0d459e2017-03-01 22:55:10158 content::WebContents* web_contents,
pkotwicz97478712017-03-01 03:39:54159 const ShortcutInfo& info,
160 const SkBitmap& icon_bitmap,
161 const WebApkInstaller::FinishCallback& callback) {
pkotwiczd0d459e2017-03-01 22:55:10162 WebApkInstallService::Get(web_contents->GetBrowserContext())
pkotwicz97478712017-03-01 03:39:54163 ->InstallAsync(info, icon_bitmap, callback);
164 // Don't record metric for users who install WebAPKs via "unsigned sources"
165 // flow.
166 if (ChromeWebApkHost::GetGooglePlayInstallState() ==
167 GooglePlayInstallState::SUPPORTED) {
168 webapk::TrackGooglePlayInstallState(GooglePlayInstallState::SUPPORTED);
169 }
170}
171
hanxi070d10a2017-01-09 15:56:50172void ShortcutHelper::ShowWebApkInstallInProgressToast() {
173 Java_ShortcutHelper_showWebApkInstallInProgressToast(
174 base::android::AttachCurrentThread());
175}
176
zpeng5d8fdfc2017-01-05 15:45:06177int ShortcutHelper::GetIdealHomescreenIconSizeInPx() {
zpenge33ba852017-02-01 20:54:42178 if (g_ideal_homescreen_icon_size == -1)
lalitm45a03c72015-09-16 13:00:43179 GetHomescreenIconAndSplashImageSizes();
zpenge33ba852017-02-01 20:54:42180 return g_ideal_homescreen_icon_size;
lalitm45a03c72015-09-16 13:00:43181}
182
zpeng5d8fdfc2017-01-05 15:45:06183int ShortcutHelper::GetMinimumHomescreenIconSizeInPx() {
zpenge33ba852017-02-01 20:54:42184 if (g_minimum_homescreen_icon_size == -1)
lalitm45a03c72015-09-16 13:00:43185 GetHomescreenIconAndSplashImageSizes();
zpenge33ba852017-02-01 20:54:42186 return g_minimum_homescreen_icon_size;
lalitm45a03c72015-09-16 13:00:43187}
188
zpeng5d8fdfc2017-01-05 15:45:06189int ShortcutHelper::GetIdealSplashImageSizeInPx() {
zpenge33ba852017-02-01 20:54:42190 if (g_ideal_splash_image_size == -1)
lalitm45a03c72015-09-16 13:00:43191 GetHomescreenIconAndSplashImageSizes();
zpenge33ba852017-02-01 20:54:42192 return g_ideal_splash_image_size;
lalitm45a03c72015-09-16 13:00:43193}
194
zpeng5d8fdfc2017-01-05 15:45:06195int ShortcutHelper::GetMinimumSplashImageSizeInPx() {
zpenge33ba852017-02-01 20:54:42196 if (g_minimum_splash_image_size == -1)
lalitm45a03c72015-09-16 13:00:43197 GetHomescreenIconAndSplashImageSizes();
zpenge33ba852017-02-01 20:54:42198 return g_minimum_splash_image_size;
199}
200
201int ShortcutHelper::GetIdealBadgeIconSizeInPx() {
202 if (g_ideal_badge_icon_size == -1)
203 GetHomescreenIconAndSplashImageSizes();
204 return g_ideal_badge_icon_size;
lalitm45a03c72015-09-16 13:00:43205}
206
lalitmd93c2ed2015-09-04 16:22:12207// static
208void ShortcutHelper::FetchSplashScreenImage(
209 content::WebContents* web_contents,
210 const GURL& image_url,
zpeng5d8fdfc2017-01-05 15:45:06211 const int ideal_splash_image_size_in_px,
212 const int minimum_splash_image_size_in_px,
dominickn6509a4de2016-04-06 08:29:06213 const std::string& webapp_id) {
lalitmd93c2ed2015-09-04 16:22:12214 // This is a fire and forget task. It is not vital for the splash screen image
215 // to be downloaded so if the downloader returns false there is no fallback.
216 ManifestIconDownloader::Download(
zpeng5d8fdfc2017-01-05 15:45:06217 web_contents, image_url, ideal_splash_image_size_in_px,
218 minimum_splash_image_size_in_px,
dominickn6509a4de2016-04-06 08:29:06219 base::Bind(&ShortcutHelper::StoreWebappSplashImage, webapp_id));
lalitmd93c2ed2015-09-04 16:22:12220}
221
222// static
zpenga7856eef2017-02-07 11:42:44223void ShortcutHelper::StoreWebappSplashImage(const std::string& webapp_id,
224 const SkBitmap& splash_image) {
lalitmd93c2ed2015-09-04 16:22:12225 if (splash_image.drawsNothing())
226 return;
227
228 JNIEnv* env = base::android::AttachCurrentThread();
229 ScopedJavaLocalRef<jstring> java_webapp_id =
230 base::android::ConvertUTF8ToJavaString(env, webapp_id);
231 ScopedJavaLocalRef<jobject> java_splash_image =
232 gfx::ConvertToJavaBitmap(&splash_image);
233
torne948f3662016-08-16 15:10:44234 Java_ShortcutHelper_storeWebappSplashImage(env, java_webapp_id,
235 java_splash_image);
lalitmd93c2ed2015-09-04 16:22:12236}
237
mlamouribc6e8792015-10-22 20:41:13238// static
pkotwicz5774087e2016-08-10 17:36:40239SkBitmap ShortcutHelper::FinalizeLauncherIconInBackground(
240 const SkBitmap& bitmap,
241 const GURL& url,
242 bool* is_generated) {
243 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
mlamouribc6e8792015-10-22 20:41:13244
245 JNIEnv* env = base::android::AttachCurrentThread();
246 ScopedJavaLocalRef<jobject> result;
247 *is_generated = false;
248
249 if (!bitmap.isNull()) {
pkotwicz45fc42b62016-06-07 00:07:10250 if (Java_ShortcutHelper_isIconLargeEnoughForLauncher(env, bitmap.width(),
251 bitmap.height())) {
newta584b9e2015-10-29 22:29:43252 ScopedJavaLocalRef<jobject> java_bitmap =
253 gfx::ConvertToJavaBitmap(&bitmap);
torne948f3662016-08-16 15:10:44254 result =
255 Java_ShortcutHelper_createHomeScreenIconFromWebIcon(env, java_bitmap);
mlamouribc6e8792015-10-22 20:41:13256 }
257 }
258
259 if (result.is_null()) {
260 ScopedJavaLocalRef<jstring> java_url =
261 base::android::ConvertUTF8ToJavaString(env, url.spec());
zpenge33ba852017-02-01 20:54:42262 SkColor mean_color =
263 SkColorSetRGB(g_default_rgb_icon_value, g_default_rgb_icon_value,
264 g_default_rgb_icon_value);
mlamouribc6e8792015-10-22 20:41:13265
266 if (!bitmap.isNull())
267 mean_color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
268
269 *is_generated = true;
newta584b9e2015-10-29 22:29:43270 result = Java_ShortcutHelper_generateHomeScreenIcon(
torne948f3662016-08-16 15:10:44271 env, java_url, SkColorGetR(mean_color), SkColorGetG(mean_color),
mlamouribc6e8792015-10-22 20:41:13272 SkColorGetB(mean_color));
273 }
274
pkotwicz964382b2016-08-04 01:24:55275 return result.obj()
torned64eb5132016-10-24 12:51:28276 ? gfx::CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(result))
pkotwicz964382b2016-08-04 01:24:55277 : SkBitmap();
mlamouribc6e8792015-10-22 20:41:13278}
279
pkotwiczcda82fe2016-07-08 18:56:54280// static
zpeng4bb58962016-10-04 02:42:29281std::string ShortcutHelper::QueryWebApkPackage(const GURL& url) {
282 JNIEnv* env = base::android::AttachCurrentThread();
283 ScopedJavaLocalRef<jstring> java_url =
284 base::android::ConvertUTF8ToJavaString(env, url.spec());
285 ScopedJavaLocalRef<jstring> java_webapk_package_name =
286 Java_ShortcutHelper_queryWebApkPackage(env, java_url);
287
288 std::string webapk_package_name = "";
289 if (java_webapk_package_name.obj()) {
zpenga7856eef2017-02-07 11:42:44290 webapk_package_name =
291 base::android::ConvertJavaStringToUTF8(env, java_webapk_package_name);
zpeng4bb58962016-10-04 02:42:29292 }
293 return webapk_package_name;
294}
295
296// static
zpenga7856eef2017-02-07 11:42:44297bool ShortcutHelper::IsWebApkInstalled(content::BrowserContext* browser_context,
298 const GURL& start_url,
299 const GURL& manifest_url) {
hanxi070d10a2017-01-09 15:56:50300 return !QueryWebApkPackage(start_url).empty() ||
zpenga7856eef2017-02-07 11:42:44301 WebApkInstallService::Get(browser_context)
302 ->IsInstallInProgress(manifest_url);
pkotwiczcda82fe2016-07-08 18:56:54303}
304
pkotwicz47136bc2016-08-06 23:55:39305GURL ShortcutHelper::GetScopeFromURL(const GURL& url) {
306 JNIEnv* env = base::android::AttachCurrentThread();
307 ScopedJavaLocalRef<jstring> java_url =
308 base::android::ConvertUTF8ToJavaString(env, url.spec());
309 ScopedJavaLocalRef<jstring> java_scope_url =
torne948f3662016-08-16 15:10:44310 Java_ShortcutHelper_getScopeFromUrl(env, java_url);
pkotwicz47136bc2016-08-06 23:55:39311 return GURL(base::android::ConvertJavaStringToUTF16(env, java_scope_url));
312}
313
gonzalon87192d772017-02-07 22:31:10314void ShortcutHelper::RetrieveWebApks(const WebApkInfoCallback& callback) {
315 uintptr_t callback_pointer =
316 reinterpret_cast<uintptr_t>(new WebApkInfoCallback(callback));
317 Java_ShortcutHelper_retrieveWebApks(base::android::AttachCurrentThread(),
318 callback_pointer);
319}
320
dominickn6509a4de2016-04-06 08:29:06321// Callback used by Java when the shortcut has been created.
322// |splash_image_callback| is a pointer to a base::Closure allocated in
pkotwiczc67e6ac2016-08-12 19:56:44323// AddShortcutWithSkBitmap, so reinterpret_cast it back and run it.
dominickn6509a4de2016-04-06 08:29:06324//
325// This callback should only ever be called when the shortcut was for a
326// webapp-capable site; otherwise, |splash_image_callback| will have never been
327// allocated and doesn't need to be run or deleted.
328void OnWebappDataStored(JNIEnv* env,
329 const JavaParamRef<jclass>& clazz,
330 jlong jsplash_image_callback) {
331 DCHECK(jsplash_image_callback);
332 base::Closure* splash_image_callback =
333 reinterpret_cast<base::Closure*>(jsplash_image_callback);
334 splash_image_callback->Run();
335 delete splash_image_callback;
336}
337
gonzalon87192d772017-02-07 22:31:10338void OnWebApksRetrieved(JNIEnv* env,
339 const JavaParamRef<jclass>& clazz,
340 const jlong jcallback_pointer,
341 const JavaParamRef<jobjectArray>& jshort_names,
342 const JavaParamRef<jobjectArray>& jpackage_names,
343 const JavaParamRef<jintArray>& jshell_apk_versions,
344 const JavaParamRef<jintArray>& jversion_codes) {
345 DCHECK(jcallback_pointer);
346 std::vector<std::string> short_names;
347 base::android::AppendJavaStringArrayToStringVector(env, jshort_names,
348 &short_names);
349 std::vector<std::string> package_names;
350 base::android::AppendJavaStringArrayToStringVector(env, jpackage_names,
351 &package_names);
352 std::vector<int> shell_apk_versions;
353 base::android::JavaIntArrayToIntVector(env, jshell_apk_versions,
354 &shell_apk_versions);
355 std::vector<int> version_codes;
356 base::android::JavaIntArrayToIntVector(env, jversion_codes, &version_codes);
357
358 DCHECK(short_names.size() == package_names.size());
359 DCHECK(short_names.size() == shell_apk_versions.size());
360 DCHECK(short_names.size() == version_codes.size());
361
362 std::vector<WebApkInfo> webapk_list;
363 webapk_list.reserve(short_names.size());
364 for (size_t i = 0; i < short_names.size(); ++i) {
365 webapk_list.push_back(WebApkInfo(std::move(short_names[i]),
366 std::move(package_names[i]),
367 shell_apk_versions[i], version_codes[i]));
368 }
369
370 ShortcutHelper::WebApkInfoCallback* webapk_list_callback =
371 reinterpret_cast<ShortcutHelper::WebApkInfoCallback*>(jcallback_pointer);
372 webapk_list_callback->Run(webapk_list);
373 delete webapk_list_callback;
374}
375
lalitm870920e2015-08-20 22:06:03376bool ShortcutHelper::RegisterShortcutHelper(JNIEnv* env) {
377 return RegisterNativesImpl(env);
benwells840ae902015-02-17 21:13:28378}