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. 4 5 #include "chrome/browser/android/shortcut_helper.h" 6 7 #include <jni.h> 8 9 #include "base/android/jni_android.h" 10 #include "base/android/jni_string.h" 11 #include "base/basictypes.h" 12 #include "base/location.h" 13 #include "base/strings/string16.h" 14 #include "base/threading/worker_pool.h" 15 #include "chrome/browser/android/tab_android.h" 16 #include "chrome/browser/favicon/favicon_service.h" 17 #include "chrome/browser/favicon/favicon_service_factory.h" 18 #include "chrome/common/cancelable_task_tracker.h" 19 #include "chrome/common/render_messages.h" 20 #include "content/public/browser/user_metrics.h" 21 #include "content/public/browser/web_contents.h" 22 #include "content/public/browser/web_contents_observer.h" 23 #include "content/public/common/frame_navigate_params.h" 24 #include "jni/ShortcutHelper_jni.h" 25 #include "ui/gfx/android/java_bitmap.h" 26 #include "ui/gfx/codec/png_codec.h" 27 #include "ui/gfx/color_analysis.h" 28 #include "ui/gfx/favicon_size.h" 29 #include "url/gurl.h" 30 31 ShortcutBuilder::ShortcutBuilder(content::WebContents* web_contents, 32 const base::string16& title, 33 int launcher_large_icon_size) 34 : launcher_large_icon_size_(launcher_large_icon_size), 35 shortcut_type_(BOOKMARK) { 36 Observe(web_contents); 37 url_ = web_contents->GetURL(); 38 if (title.length() > 0) 39 title_ = title; 40 else 41 title_ = web_contents->GetTitle(); 42 43 // Send a message to the renderer to retrieve information about the page. 44 Send(new ChromeViewMsg_RetrieveWebappInformation(routing_id(), url_)); 45 } 46 47 void ShortcutBuilder::OnDidRetrieveWebappInformation( 48 bool success, 49 bool is_mobile_webapp_capable, 50 bool is_apple_mobile_webapp_capable, 51 const GURL& expected_url) { 52 Profile* profile = 53 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 54 Observe(NULL); 55 56 if (!success) { 57 LOG(ERROR) << "Failed to parse webpage."; 58 Destroy(); 59 return; 60 } else if (expected_url != url_) { 61 LOG(ERROR) << "Unexpected URL returned."; 62 Destroy(); 63 return; 64 } 65 66 if (is_apple_mobile_webapp_capable && !is_mobile_webapp_capable) { 67 shortcut_type_ = APP_SHORTCUT_APPLE; 68 } else if (is_apple_mobile_webapp_capable || is_mobile_webapp_capable) { 69 shortcut_type_ = APP_SHORTCUT; 70 } else { 71 shortcut_type_ = BOOKMARK; 72 } 73 74 // Grab the best, largest icon we can find to represent this bookmark. 75 // TODO(dfalcantara): Try combining with the new BookmarksHandler once its 76 // rewrite is further along. 77 std::vector<int> icon_types; 78 icon_types.push_back(chrome::FAVICON); 79 icon_types.push_back(chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON); 80 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile( 81 profile, Profile::EXPLICIT_ACCESS); 82 83 // Using favicon if its size is not smaller than platform required size, 84 // otherwise using the largest icon among all avaliable icons. 85 int threshold_to_get_any_largest_icon = launcher_large_icon_size_ - 1; 86 favicon_service->GetLargestRawFaviconForURL(profile, url_, icon_types, 87 threshold_to_get_any_largest_icon, 88 base::Bind(&ShortcutBuilder::FinishAddingShortcut, 89 base::Unretained(this)), 90 &cancelable_task_tracker_); 91 } 92 93 void ShortcutBuilder::FinishAddingShortcut( 94 const chrome::FaviconBitmapResult& bitmap_result) { 95 base::WorkerPool::PostTask( 96 FROM_HERE, 97 base::Bind(&ShortcutHelper::AddShortcutInBackground, 98 url_, 99 title_, 100 shortcut_type_, 101 bitmap_result), 102 true); 103 Destroy(); 104 } 105 106 bool ShortcutBuilder::OnMessageReceived(const IPC::Message& message) { 107 bool handled = true; 108 IPC_BEGIN_MESSAGE_MAP(ShortcutBuilder, message) 109 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidRetrieveWebappInformation, 110 OnDidRetrieveWebappInformation) 111 IPC_MESSAGE_UNHANDLED(handled = false) 112 IPC_END_MESSAGE_MAP() 113 return handled; 114 } 115 116 void ShortcutBuilder::WebContentsDestroyed(content::WebContents* web_contents) { 117 Destroy(); 118 } 119 120 void ShortcutBuilder::Destroy() { 121 if (cancelable_task_tracker_.HasTrackedTasks()) { 122 cancelable_task_tracker_.TryCancelAll(); 123 } 124 delete this; 125 } 126 127 void ShortcutHelper::AddShortcut(content::WebContents* web_contents, 128 const base::string16& title, 129 int launcher_large_icon_size) { 130 // The ShortcutBuilder deletes itself when it's done. 131 new ShortcutBuilder(web_contents, title, launcher_large_icon_size); 132 } 133 134 bool ShortcutHelper::RegisterShortcutHelper(JNIEnv* env) { 135 return RegisterNativesImpl(env); 136 } 137 138 void ShortcutHelper::AddShortcutInBackground( 139 const GURL& url, 140 const base::string16& title, 141 ShortcutBuilder::ShortcutType shortcut_type, 142 const chrome::FaviconBitmapResult& bitmap_result) { 143 DCHECK(base::WorkerPool::RunsTasksOnCurrentThread()); 144 145 // Grab the average color from the bitmap. 146 SkColor color = SK_ColorWHITE; 147 SkBitmap favicon_bitmap; 148 if (bitmap_result.is_valid()) { 149 color_utils::GridSampler sampler; 150 color = color_utils::CalculateKMeanColorOfPNG(bitmap_result.bitmap_data, 151 100, 152 665, 153 &sampler); 154 gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(), 155 bitmap_result.bitmap_data->size(), 156 &favicon_bitmap); 157 } 158 159 int r_value = SkColorGetR(color); 160 int g_value = SkColorGetG(color); 161 int b_value = SkColorGetB(color); 162 163 // Send the data to the Java side to create the shortcut. 164 JNIEnv* env = base::android::AttachCurrentThread(); 165 ScopedJavaLocalRef<jstring> java_url = 166 base::android::ConvertUTF8ToJavaString(env, url.spec()); 167 ScopedJavaLocalRef<jstring> java_title = 168 base::android::ConvertUTF16ToJavaString(env, title); 169 ScopedJavaLocalRef<jobject> java_bitmap; 170 if (favicon_bitmap.getSize()) 171 java_bitmap = gfx::ConvertToJavaBitmap(&favicon_bitmap); 172 173 Java_ShortcutHelper_addShortcut(env, 174 base::android::GetApplicationContext(), 175 java_url.obj(), 176 java_title.obj(), 177 java_bitmap.obj(), 178 r_value, 179 g_value, 180 b_value, 181 shortcut_type != ShortcutBuilder::BOOKMARK); 182 183 // Record what type of shortcut was added by the user. 184 switch (shortcut_type) { 185 case ShortcutBuilder::APP_SHORTCUT: 186 content::RecordAction( 187 content::UserMetricsAction("webapps.AddShortcut.AppShortcut")); 188 break; 189 case ShortcutBuilder::APP_SHORTCUT_APPLE: 190 content::RecordAction( 191 content::UserMetricsAction("webapps.AddShortcut.AppShortcutApple")); 192 break; 193 case ShortcutBuilder::BOOKMARK: 194 content::RecordAction( 195 content::UserMetricsAction("webapps.AddShortcut.Bookmark")); 196 break; 197 default: 198 NOTREACHED(); 199 } 200 } 201 202 // Adds a shortcut to the current URL to the Android home screen, firing 203 // background tasks to pull all the data required. 204 // Note that we don't actually care about the tab here -- we just want 205 // its otherwise inaccessible WebContents. 206 static void AddShortcut(JNIEnv* env, 207 jclass clazz, 208 jlong tab_android_ptr, 209 jstring title, 210 jint launcher_large_icon_size) { 211 TabAndroid* tab = reinterpret_cast<TabAndroid*>(tab_android_ptr); 212 ShortcutHelper::AddShortcut( 213 tab->web_contents(), 214 base::android::ConvertJavaStringToUTF16(env, title), 215 launcher_large_icon_size); 216 } 217