1 // 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.
5 #include "chrome/browser/android/shortcut_helper.h"
10 #include "base/android/jni_android.h"
11 #include "base/android/jni_string.h"
12 #include "base/basictypes.h"
13 #include "base/location.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/task/cancelable_task_tracker.h"
17 #include "base/threading/worker_pool.h"
18 #include "chrome/browser/android/manifest_icon_selector.h"
19 #include "chrome/browser/banners/app_banner_settings_helper.h"
20 #include "chrome/browser/favicon/favicon_service.h"
21 #include "chrome/browser/favicon/favicon_service_factory.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/chrome_constants.h"
24 #include "chrome/common/render_messages.h"
25 #include "chrome/common/web_application_info.h"
26 #include "components/dom_distiller/core/url_utils.h"
27 #include "content/public/browser/user_metrics.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/browser/web_contents_observer.h"
30 #include "content/public/common/frame_navigate_params.h"
31 #include "content/public/common/manifest.h"
32 #include "jni/ShortcutHelper_jni.h"
33 #include "net/base/mime_util.h"
34 #include "third_party/WebKit/public/platform/WebScreenOrientationLockType.h"
35 #include "ui/gfx/android/java_bitmap.h"
36 #include "ui/gfx/codec/png_codec.h"
37 #include "ui/gfx/color_analysis.h"
38 #include "ui/gfx/favicon_size.h"
39 #include "ui/gfx/screen.h"
42 using content::Manifest
;
44 // Android's preferred icon size in DP is 48, as defined in
45 // http://developer.android.com/design/style/iconography.html
46 const int ShortcutHelper::kPreferredIconSizeInDp
= 48;
48 jlong
Initialize(JNIEnv
* env
, jobject obj
, jobject java_web_contents
) {
49 content::WebContents
* web_contents
=
50 content::WebContents::FromJavaWebContents(java_web_contents
);
51 ShortcutHelper
* shortcut_helper
= new ShortcutHelper(env
, obj
, web_contents
);
52 shortcut_helper
->Initialize();
54 return reinterpret_cast<intptr_t>(shortcut_helper
);
57 ShortcutHelper::ShortcutHelper(JNIEnv
* env
,
59 content::WebContents
* web_contents
)
60 : WebContentsObserver(web_contents
),
62 shortcut_info_(dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
63 web_contents
->GetURL())),
64 add_shortcut_requested_(false),
65 manifest_icon_status_(MANIFEST_ICON_STATUS_NONE
),
66 preferred_icon_size_in_px_(kPreferredIconSizeInDp
*
67 gfx::Screen::GetScreenFor(web_contents
->GetNativeView())->
68 GetPrimaryDisplay().device_scale_factor()),
69 weak_ptr_factory_(this) {
72 void ShortcutHelper::Initialize() {
73 // Send a message to the renderer to retrieve information about the page.
74 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
77 ShortcutHelper::~ShortcutHelper() {
80 void ShortcutHelper::OnDidGetWebApplicationInfo(
81 const WebApplicationInfo
& received_web_app_info
) {
82 // Sanitize received_web_app_info.
83 WebApplicationInfo web_app_info
= received_web_app_info
;
85 web_app_info
.title
.substr(0, chrome::kMaxMetaTagAttributeLength
);
86 web_app_info
.description
=
87 web_app_info
.description
.substr(0, chrome::kMaxMetaTagAttributeLength
);
89 shortcut_info_
.title
= web_app_info
.title
.empty() ? web_contents()->GetTitle()
92 if (web_app_info
.mobile_capable
== WebApplicationInfo::MOBILE_CAPABLE
||
93 web_app_info
.mobile_capable
== WebApplicationInfo::MOBILE_CAPABLE_APPLE
) {
94 shortcut_info_
.display
= content::Manifest::DISPLAY_MODE_STANDALONE
;
97 // Record what type of shortcut was added by the user.
98 switch (web_app_info
.mobile_capable
) {
99 case WebApplicationInfo::MOBILE_CAPABLE
:
100 content::RecordAction(
101 base::UserMetricsAction("webapps.AddShortcut.AppShortcut"));
103 case WebApplicationInfo::MOBILE_CAPABLE_APPLE
:
104 content::RecordAction(
105 base::UserMetricsAction("webapps.AddShortcut.AppShortcutApple"));
107 case WebApplicationInfo::MOBILE_CAPABLE_UNSPECIFIED
:
108 content::RecordAction(
109 base::UserMetricsAction("webapps.AddShortcut.Bookmark"));
113 web_contents()->GetManifest(base::Bind(&ShortcutHelper::OnDidGetManifest
,
114 weak_ptr_factory_
.GetWeakPtr()));
117 void ShortcutHelper::OnDidGetManifest(const content::Manifest
& manifest
) {
118 if (!manifest
.IsEmpty()) {
119 content::RecordAction(
120 base::UserMetricsAction("webapps.AddShortcut.Manifest"));
123 shortcut_info_
.UpdateFromManifest(manifest
);
125 GURL icon_src
= ManifestIconSelector::FindBestMatchingIcon(
127 kPreferredIconSizeInDp
,
128 gfx::Screen::GetScreenFor(web_contents()->GetNativeView()));
129 if (icon_src
.is_valid()) {
130 web_contents()->DownloadImage(icon_src
,
132 preferred_icon_size_in_px_
,
133 base::Bind(&ShortcutHelper::OnDidDownloadIcon
,
134 weak_ptr_factory_
.GetWeakPtr()));
135 manifest_icon_status_
= MANIFEST_ICON_STATUS_FETCHING
;
138 // The ShortcutHelper is now able to notify its Java counterpart that it is
139 // initialized. OnInitialized method is not conceptually part of getting the
140 // manifest data but it happens that the initialization is finalized when
141 // these data are available.
142 JNIEnv
* env
= base::android::AttachCurrentThread();
143 ScopedJavaLocalRef
<jobject
> j_obj
= java_ref_
.get(env
);
144 ScopedJavaLocalRef
<jstring
> j_title
=
145 base::android::ConvertUTF16ToJavaString(env
, shortcut_info_
.title
);
147 Java_ShortcutHelper_onInitialized(env
, j_obj
.obj(), j_title
.obj());
150 void ShortcutHelper::OnDidDownloadIcon(int id
,
151 int http_status_code
,
153 const std::vector
<SkBitmap
>& bitmaps
,
154 const std::vector
<gfx::Size
>& sizes
) {
155 // If getting the candidate manifest icon failed, the ShortcutHelper should
156 // fallback to the favicon.
157 // If the user already requested to add the shortcut, it will do so but use
158 // the favicon instead.
159 // Otherwise, it sets the state as if there was no manifest icon pending.
160 if (bitmaps
.empty()) {
161 if (add_shortcut_requested_
)
162 AddShortcutUsingFavicon();
164 manifest_icon_status_
= MANIFEST_ICON_STATUS_NONE
;
168 // There might be multiple bitmaps returned. The one to pick is bigger or
169 // equal to the preferred size. |bitmaps| is ordered from bigger to smaller.
170 int preferred_bitmap_index
= 0;
171 for (size_t i
= 0; i
< bitmaps
.size(); ++i
) {
172 if (bitmaps
[i
].height() < preferred_icon_size_in_px_
)
174 preferred_bitmap_index
= i
;
177 manifest_icon_
= bitmaps
[preferred_bitmap_index
];
178 manifest_icon_status_
= MANIFEST_ICON_STATUS_DONE
;
180 if (add_shortcut_requested_
)
181 AddShortcutUsingManifestIcon();
184 void ShortcutHelper::TearDown(JNIEnv
*, jobject
) {
188 void ShortcutHelper::Destroy() {
192 void ShortcutHelper::AddShortcut(
196 jint launcher_large_icon_size
) {
197 add_shortcut_requested_
= true;
199 base::string16 title
= base::android::ConvertJavaStringToUTF16(env
, jtitle
);
201 shortcut_info_
.title
= title
;
203 switch (manifest_icon_status_
) {
204 case MANIFEST_ICON_STATUS_NONE
:
205 AddShortcutUsingFavicon();
207 case MANIFEST_ICON_STATUS_FETCHING
:
208 // ::OnDidDownloadIcon() will call AddShortcutUsingManifestIcon().
210 case MANIFEST_ICON_STATUS_DONE
:
211 AddShortcutUsingManifestIcon();
216 void ShortcutHelper::AddShortcutUsingManifestIcon() {
217 RecordAddToHomescreen();
219 // Stop observing so we don't get destroyed while doing the last steps.
222 base::WorkerPool::PostTask(
224 base::Bind(&ShortcutHelper::AddShortcutInBackgroundWithSkBitmap
,
233 void ShortcutHelper::AddShortcutUsingFavicon() {
234 RecordAddToHomescreen();
237 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
239 // Grab the best, largest icon we can find to represent this bookmark.
240 // TODO(dfalcantara): Try combining with the new BookmarksHandler once its
241 // rewrite is further along.
242 std::vector
<int> icon_types
;
243 icon_types
.push_back(favicon_base::FAVICON
);
244 icon_types
.push_back(favicon_base::TOUCH_PRECOMPOSED_ICON
|
245 favicon_base::TOUCH_ICON
);
246 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
247 profile
, ServiceAccessType::EXPLICIT_ACCESS
);
249 // Using favicon if its size is not smaller than platform required size,
250 // otherwise using the largest icon among all avaliable icons.
251 int threshold_to_get_any_largest_icon
= preferred_icon_size_in_px_
- 1;
252 favicon_service
->GetLargestRawFaviconForPageURL(
255 threshold_to_get_any_largest_icon
,
256 base::Bind(&ShortcutHelper::OnDidGetFavicon
,
257 base::Unretained(this)),
258 &cancelable_task_tracker_
);
261 void ShortcutHelper::OnDidGetFavicon(
262 const favicon_base::FaviconRawBitmapResult
& bitmap_result
) {
263 // Stop observing so we don't get destroyed while doing the last steps.
266 base::WorkerPool::PostTask(
268 base::Bind(&ShortcutHelper::AddShortcutInBackgroundWithRawBitmap
,
276 bool ShortcutHelper::OnMessageReceived(const IPC::Message
& message
) {
279 IPC_BEGIN_MESSAGE_MAP(ShortcutHelper
, message
)
280 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo
,
281 OnDidGetWebApplicationInfo
)
282 IPC_MESSAGE_UNHANDLED(handled
= false)
283 IPC_END_MESSAGE_MAP()
288 void ShortcutHelper::WebContentsDestroyed() {
292 bool ShortcutHelper::RegisterShortcutHelper(JNIEnv
* env
) {
293 return RegisterNativesImpl(env
);
296 void ShortcutHelper::AddShortcutInBackgroundWithRawBitmap(
297 const ShortcutInfo
& info
,
298 const favicon_base::FaviconRawBitmapResult
& bitmap_result
) {
299 DCHECK(base::WorkerPool::RunsTasksOnCurrentThread());
301 SkBitmap icon_bitmap
;
302 if (bitmap_result
.is_valid()) {
303 gfx::PNGCodec::Decode(bitmap_result
.bitmap_data
->front(),
304 bitmap_result
.bitmap_data
->size(),
308 AddShortcutInBackgroundWithSkBitmap(info
, icon_bitmap
, true);
311 void ShortcutHelper::AddShortcutInBackgroundWithSkBitmap(
312 const ShortcutInfo
& info
,
313 const SkBitmap
& icon_bitmap
,
314 const bool return_to_homescreen
) {
315 DCHECK(base::WorkerPool::RunsTasksOnCurrentThread());
317 SkColor color
= color_utils::CalculateKMeanColorOfBitmap(icon_bitmap
);
318 int r_value
= SkColorGetR(color
);
319 int g_value
= SkColorGetG(color
);
320 int b_value
= SkColorGetB(color
);
322 // Send the data to the Java side to create the shortcut.
323 JNIEnv
* env
= base::android::AttachCurrentThread();
324 ScopedJavaLocalRef
<jstring
> java_url
=
325 base::android::ConvertUTF8ToJavaString(env
, info
.url
.spec());
326 ScopedJavaLocalRef
<jstring
> java_title
=
327 base::android::ConvertUTF16ToJavaString(env
, info
.title
);
328 ScopedJavaLocalRef
<jobject
> java_bitmap
;
329 if (icon_bitmap
.getSize())
330 java_bitmap
= gfx::ConvertToJavaBitmap(&icon_bitmap
);
332 Java_ShortcutHelper_addShortcut(
334 base::android::GetApplicationContext(),
341 info
.display
== content::Manifest::DISPLAY_MODE_STANDALONE
,
343 return_to_homescreen
);
346 void ShortcutHelper::RecordAddToHomescreen() {
347 // Record that the shortcut has been added, so no banners will be shown
349 AppBannerSettingsHelper::RecordBannerEvent(
350 web_contents(), shortcut_info_
.url
, shortcut_info_
.url
.spec(),
351 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN
,