[Webapps] Allow adding shortcuts to the launcher
Adds a way to add shortcuts to the Android launcher that avoids going
through the bookmarks menu. Once the feature is further along, we'll
remove the difficult-to-find pathway through the bookmarks menu.
New files:
* ShortcutHelper (C++):
WebContentsObserver that goes through the process of adding a
shortcut to the homescreen. It proceeds in three phases, which
include making an IPC call to the renderer to retrieve webapp
meta tag information and securing a high-resolution icon to use
for the shortcut.
* ShortcutHelper (Java):
Actually fires the Intent to add a shortcut to the Android
launcher.
Changes:
* TabAndroid now creates a ShortcutHelper, in addition to its other
helpers.
* ChromeRenderViewObserver watches for IPC calls to parse the
DOM to find webapp-related meta tags and sends that information
back.
BUG=266403
Review URL: https://chromiumcodereview.appspot.com/22707003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@218317 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
new file mode 100644
index 0000000..58206bed
--- /dev/null
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -0,0 +1,200 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/shortcut_helper.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/basictypes.h"
+#include "base/location.h"
+#include "base/strings/string16.h"
+#include "base/threading/worker_pool.h"
+#include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/favicon/favicon_service.h"
+#include "chrome/browser/favicon/favicon_service_factory.h"
+#include "chrome/common/cancelable_task_tracker.h"
+#include "chrome/common/render_messages.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/frame_navigate_params.h"
+#include "jni/ShortcutHelper_jni.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/color_analysis.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Adds a shortcut to the current URL to the Android home screen.
+// This proceeds over three phases:
+// 1) The renderer is asked to parse out webapp related meta tags with an async
+// IPC message.
+// 2) The highest-resolution favicon available is retrieved for use as the
+// icon on the home screen.
+// 3) A JNI call is made to fire an Intent at the Android launcher, which adds
+// adds the shortcut.
+class ShortcutBuilder : public content::WebContentsObserver {
+ public:
+ explicit ShortcutBuilder(content::WebContents* web_contents);
+ virtual ~ShortcutBuilder();
+
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE;
+
+ private:
+ void OnDidRetrieveWebappInformation(bool success, bool is_webapp_capable);
+
+ Profile* profile_;
+ GURL url_;
+ string16 title_;
+ bool is_webapp_capable_;
+ CancelableTaskTracker cancelable_task_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShortcutBuilder);
+};
+
+ShortcutBuilder::ShortcutBuilder(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents) {
+ profile_ =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ url_ = web_contents->GetURL();
+ title_ = web_contents->GetTitle();
+
+ // Send a message to the renderer to retrieve information about the page.
+ Send(new ChromeViewMsg_RetrieveWebappInformation(routing_id(), url_));
+}
+
+ShortcutBuilder::~ShortcutBuilder() {
+}
+
+void ShortcutBuilder::OnDidRetrieveWebappInformation(bool success,
+ bool is_webapp_capable) {
+ // Abort adding the shortcut if the renderer failed to process the page or
+ // if a navigation was instantiated before the process finished.
+ if (!success) {
+ LOG(ERROR) << "Failed to parse webpage.";
+ return;
+ }
+ is_webapp_capable_ = is_webapp_capable;
+
+ // Grab the best, largest icon we can find to represent this bookmark.
+ // TODO(dfalcantara): Try combining with the new BookmarksHandler once its
+ // rewrite is further along.
+ FaviconService::FaviconForURLParams favicon_params(
+ profile_,
+ url_,
+ chrome::TOUCH_PRECOMPOSED_ICON | chrome::TOUCH_ICON | chrome::FAVICON,
+ 0);
+
+ FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
+ profile_, Profile::EXPLICIT_ACCESS);
+
+ favicon_service->GetRawFaviconForURL(
+ favicon_params,
+ ui::SCALE_FACTOR_100P,
+ base::Bind(&ShortcutHelper::FinishAddingShortcut,
+ url_,
+ title_,
+ is_webapp_capable_),
+ &cancelable_task_tracker_);
+}
+
+void ShortcutBuilder::WebContentsDestroyed(content::WebContents* web_contents) {
+ if (cancelable_task_tracker_.HasTrackedTasks())
+ cancelable_task_tracker_.TryCancelAll();
+ delete this;
+}
+
+bool ShortcutBuilder::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ShortcutBuilder, message)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidRetrieveWebappInformation,
+ OnDidRetrieveWebappInformation)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+} // namespace
+
+void ShortcutHelper::AddShortcut(content::WebContents* web_contents) {
+ // The ShortcutBuilder deletes itself when it's done.
+ new ShortcutBuilder(web_contents);
+}
+
+bool ShortcutHelper::RegisterShortcutHelper(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void ShortcutHelper::FinishAddingShortcut(
+ const GURL& url,
+ const string16& title,
+ bool is_webapp_capable,
+ const chrome::FaviconBitmapResult& bitmap_result) {
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&ShortcutHelper::FinishAddingShortcutInBackground,
+ url,
+ title,
+ is_webapp_capable,
+ bitmap_result),
+ true);
+}
+
+void ShortcutHelper::FinishAddingShortcutInBackground(
+ const GURL& url,
+ const string16& title,
+ bool is_webapp_capable,
+ const chrome::FaviconBitmapResult& bitmap_result) {
+ DCHECK(base::WorkerPool::RunsTasksOnCurrentThread());
+
+ // Grab the average color from the bitmap.
+ SkColor color = SK_ColorWHITE;
+ SkBitmap favicon_bitmap;
+ if (bitmap_result.is_valid()) {
+ color_utils::GridSampler sampler;
+ color = color_utils::CalculateKMeanColorOfPNG(bitmap_result.bitmap_data,
+ 100,
+ 665,
+ &sampler);
+ gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
+ bitmap_result.bitmap_data->size(),
+ &favicon_bitmap);
+ }
+
+ int r_value = SkColorGetR(color);
+ int g_value = SkColorGetG(color);
+ int b_value = SkColorGetB(color);
+
+ // Send the data to the Java side to create the shortcut.
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> java_url =
+ base::android::ConvertUTF8ToJavaString(env, url.spec());
+ ScopedJavaLocalRef<jstring> java_title =
+ base::android::ConvertUTF16ToJavaString(env, title);
+ ScopedJavaLocalRef<jobject> java_bitmap;
+ if (favicon_bitmap.getSize())
+ java_bitmap = gfx::ConvertToJavaBitmap(&favicon_bitmap);
+
+ Java_ShortcutHelper_addShortcut(env,
+ base::android::GetApplicationContext(),
+ java_url.obj(),
+ java_title.obj(),
+ java_bitmap.obj(),
+ r_value,
+ g_value,
+ b_value,
+ is_webapp_capable);
+}
+
+// Adds a shortcut to the current URL to the Android home screen, firing
+// background tasks to pull all the data required.
+// Note that we don't actually care about the tab here -- we just want
+// its otherwise inaccessible WebContents.
+static void AddShortcut(JNIEnv* env, jclass clazz, jint tab_android_ptr) {
+ TabAndroid* tab = reinterpret_cast<TabAndroid*>(tab_android_ptr);
+ ShortcutHelper::AddShortcut(tab->GetWebContents());
+}