ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / android / banners / app_banner_manager.cc
blob0283d2e58d5632e067b59ce96b2b6236501163e9
1 // Copyright 2014 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/banners/app_banner_manager.h"
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_string.h"
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/string_util.h"
14 #include "chrome/browser/android/banners/app_banner_infobar_delegate.h"
15 #include "chrome/browser/android/manifest_icon_selector.h"
16 #include "chrome/browser/android/shortcut_helper.h"
17 #include "chrome/browser/android/shortcut_info.h"
18 #include "chrome/browser/banners/app_banner_metrics.h"
19 #include "chrome/browser/banners/app_banner_settings_helper.h"
20 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
21 #include "chrome/browser/infobars/infobar_service.h"
22 #include "chrome/browser/metrics/rappor/sampling.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/common/chrome_constants.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/render_messages.h"
27 #include "content/public/browser/android/content_view_core.h"
28 #include "content/public/browser/browser_context.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/navigation_details.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/service_worker_context.h"
33 #include "content/public/browser/storage_partition.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/common/frame_navigate_params.h"
36 #include "content/public/common/manifest.h"
37 #include "jni/AppBannerManager_jni.h"
38 #include "net/base/load_flags.h"
39 #include "ui/gfx/android/java_bitmap.h"
40 #include "ui/gfx/screen.h"
42 using base::android::ConvertJavaStringToUTF8;
43 using base::android::ConvertJavaStringToUTF16;
44 using base::android::ConvertUTF8ToJavaString;
45 using base::android::ConvertUTF16ToJavaString;
47 namespace {
49 const char kBannerTag[] = "google-play-id";
50 base::TimeDelta gTimeDeltaForTesting;
51 bool gDisableSecureCheckForTesting = false;
52 const int kIconMinimumSize = 144;
54 // The requirement for now is an image/png that is at least 144x144.
55 bool DoesManifestContainRequiredIcon(const content::Manifest& manifest) {
56 for (const auto& icon : manifest.icons) {
57 if (!EqualsASCII(icon.type.string(), "image/png"))
58 continue;
60 for (const auto& size : icon.sizes) {
61 if (size.IsEmpty()) // "any"
62 return true;
63 if (size.width() >= kIconMinimumSize && size.height() >= kIconMinimumSize)
64 return true;
68 return false;
71 } // anonymous namespace
73 namespace banners {
75 // Fetches a bitmap and deletes itself when completed.
76 class AppBannerManager::BannerBitmapFetcher
77 : public chrome::BitmapFetcher,
78 public chrome::BitmapFetcherDelegate {
79 public:
80 BannerBitmapFetcher(const GURL& image_url,
81 banners::AppBannerManager* manager);
83 // Prevents informing the AppBannerManager that the fetch has completed.
84 void Cancel();
86 // chrome::BitmapFetcherDelegate overrides
87 void OnFetchComplete(const GURL url, const SkBitmap* icon) override;
89 private:
90 banners::AppBannerManager* manager_;
91 bool is_cancelled_;
94 AppBannerManager::BannerBitmapFetcher::BannerBitmapFetcher(
95 const GURL& image_url,
96 banners::AppBannerManager* manager)
97 : chrome::BitmapFetcher(image_url, this),
98 manager_(manager),
99 is_cancelled_(false) {
102 void AppBannerManager::BannerBitmapFetcher::Cancel() {
103 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
104 is_cancelled_ = true;
107 void AppBannerManager::BannerBitmapFetcher::OnFetchComplete(
108 const GURL url,
109 const SkBitmap* icon) {
110 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
112 if (!is_cancelled_)
113 manager_->OnFetchComplete(this, url, icon);
115 delete this;
118 AppBannerManager::AppBannerManager(JNIEnv* env, jobject obj, int icon_size)
119 : preferred_icon_size_(icon_size),
120 fetcher_(nullptr),
121 weak_java_banner_view_manager_(env, obj),
122 weak_factory_(this) {
125 AppBannerManager::~AppBannerManager() {
126 CancelActiveFetcher();
129 void AppBannerManager::Destroy(JNIEnv* env, jobject obj) {
130 delete this;
133 void AppBannerManager::ReplaceWebContents(JNIEnv* env,
134 jobject obj,
135 jobject jweb_contents) {
136 content::WebContents* web_contents =
137 content::WebContents::FromJavaWebContents(jweb_contents);
138 Observe(web_contents);
141 void AppBannerManager::DidNavigateMainFrame(
142 const content::LoadCommittedDetails& details,
143 const content::FrameNavigateParams& params) {
144 // Clear current state.
145 CancelActiveFetcher();
146 app_title_ = base::string16();
147 web_app_data_ = content::Manifest();
148 native_app_data_.Reset();
149 native_app_package_ = std::string();
152 void AppBannerManager::DidFinishLoad(
153 content::RenderFrameHost* render_frame_host,
154 const GURL& validated_url) {
155 if (render_frame_host->GetParent())
156 return;
157 validated_url_ = validated_url;
159 // A secure scheme is required to show banners, so exit early if we see the
160 // URL is invalid.
161 if (!validated_url_.SchemeIsSecure() && !gDisableSecureCheckForTesting)
162 return;
164 // See if the page has a manifest.
165 web_contents()->GetManifest(base::Bind(&AppBannerManager::OnDidGetManifest,
166 weak_factory_.GetWeakPtr()));
169 // static
170 bool AppBannerManager::IsManifestValid(const content::Manifest& manifest) {
171 if (manifest.IsEmpty())
172 return false;
173 if (!manifest.start_url.is_valid())
174 return false;
175 if (manifest.name.is_null() && manifest.short_name.is_null())
176 return false;
177 if (!DoesManifestContainRequiredIcon(manifest))
178 return false;
180 return true;
183 void AppBannerManager::OnDidGetManifest(const content::Manifest& manifest) {
184 if (web_contents()->IsBeingDestroyed())
185 return;
187 if (!IsManifestValid(manifest)) {
188 // No usable manifest, see if there is a play store meta tag.
189 if (!IsEnabledForNativeApps())
190 return;
192 Send(new ChromeViewMsg_RetrieveMetaTagContent(routing_id(),
193 validated_url_,
194 kBannerTag));
195 return;
198 banners::TrackDisplayEvent(DISPLAY_EVENT_BANNER_REQUESTED);
200 web_app_data_ = manifest;
201 app_title_ = web_app_data_.name.string();
203 // Check to see if there is a single service worker controlling this page
204 // and the manifest's start url.
205 Profile* profile =
206 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
207 content::StoragePartition* storage_partition =
208 content::BrowserContext::GetStoragePartition(
209 profile, web_contents()->GetSiteInstance());
210 DCHECK(storage_partition);
212 storage_partition->GetServiceWorkerContext()->CheckHasServiceWorker(
213 validated_url_, manifest.start_url,
214 base::Bind(&AppBannerManager::OnDidCheckHasServiceWorker,
215 weak_factory_.GetWeakPtr()));
218 void AppBannerManager::OnDidCheckHasServiceWorker(bool has_service_worker) {
219 if (has_service_worker) {
220 // TODO(benwells): Check triggering parameters.
221 // Create an infobar to promote the manifest's app.
222 GURL icon_url =
223 ManifestIconSelector::FindBestMatchingIcon(
224 web_app_data_.icons,
225 preferred_icon_size_,
226 gfx::Screen::GetScreenFor(web_contents()->GetNativeView()));
227 if (icon_url.is_empty())
228 return;
230 FetchIcon(icon_url);
231 } else {
232 TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
236 void AppBannerManager::RecordCouldShowBanner(
237 const std::string& package_or_start_url) {
238 AppBannerSettingsHelper::RecordBannerEvent(
239 web_contents(), validated_url_, package_or_start_url,
240 AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW, GetCurrentTime());
243 bool AppBannerManager::CheckIfShouldShow(
244 const std::string& package_or_start_url) {
245 if (!AppBannerSettingsHelper::ShouldShowBanner(web_contents(), validated_url_,
246 package_or_start_url,
247 GetCurrentTime())) {
248 return false;
251 AppBannerSettingsHelper::RecordBannerEvent(
252 web_contents(), validated_url_, package_or_start_url,
253 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime());
254 return true;
257 void AppBannerManager::CancelActiveFetcher() {
258 // Fetchers delete themselves.
259 if (fetcher_ != nullptr) {
260 fetcher_->Cancel();
261 fetcher_ = nullptr;
265 bool AppBannerManager::OnMessageReceived(const IPC::Message& message) {
266 bool handled = true;
267 IPC_BEGIN_MESSAGE_MAP(AppBannerManager, message)
268 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidRetrieveMetaTagContent,
269 OnDidRetrieveMetaTagContent)
270 IPC_MESSAGE_UNHANDLED(handled = false)
271 IPC_END_MESSAGE_MAP()
272 return handled;
275 void AppBannerManager::OnFetchComplete(BannerBitmapFetcher* fetcher,
276 const GURL url,
277 const SkBitmap* bitmap) {
278 if (fetcher_ != fetcher)
279 return;
280 fetcher_ = nullptr;
282 if (!web_contents()
283 || web_contents()->IsBeingDestroyed()
284 || validated_url_ != web_contents()->GetURL()
285 || !bitmap
286 || url != app_icon_url_) {
287 return;
290 JNIEnv* env = base::android::AttachCurrentThread();
291 ScopedJavaLocalRef<jobject> jobj = weak_java_banner_view_manager_.get(env);
292 if (jobj.is_null())
293 return;
295 InfoBarService* service = InfoBarService::FromWebContents(web_contents());
297 AppBannerInfoBar* weak_infobar_ptr = nullptr;
298 if (!native_app_data_.is_null()) {
299 RecordCouldShowBanner(native_app_package_);
300 if (!CheckIfShouldShow(native_app_package_))
301 return;
303 weak_infobar_ptr = AppBannerInfoBarDelegate::CreateForNativeApp(
304 service,
305 app_title_,
306 new SkBitmap(*bitmap),
307 native_app_data_,
308 native_app_package_);
310 rappor::SampleDomainAndRegistryFromGURL("AppBanner.NativeApp.Shown",
311 web_contents()->GetURL());
312 } else if (!web_app_data_.IsEmpty()){
313 RecordCouldShowBanner(web_app_data_.start_url.spec());
314 if (!CheckIfShouldShow(web_app_data_.start_url.spec()))
315 return;
317 weak_infobar_ptr = AppBannerInfoBarDelegate::CreateForWebApp(
318 service,
319 app_title_,
320 new SkBitmap(*bitmap),
321 web_app_data_);
323 rappor::SampleDomainAndRegistryFromGURL("AppBanner.WebApp.Shown",
324 web_contents()->GetURL());
327 if (weak_infobar_ptr != nullptr)
328 banners::TrackDisplayEvent(DISPLAY_EVENT_CREATED);
331 void AppBannerManager::OnDidRetrieveMetaTagContent(
332 bool success,
333 const std::string& tag_name,
334 const std::string& tag_content,
335 const GURL& expected_url) {
336 DCHECK(web_contents());
337 if (!success || tag_name != kBannerTag || validated_url_ != expected_url ||
338 tag_content.size() >= chrome::kMaxMetaTagAttributeLength) {
339 return;
342 banners::TrackDisplayEvent(DISPLAY_EVENT_BANNER_REQUESTED);
344 // Send the info to the Java side to get info about the app.
345 JNIEnv* env = base::android::AttachCurrentThread();
346 ScopedJavaLocalRef<jobject> jobj = weak_java_banner_view_manager_.get(env);
347 if (jobj.is_null())
348 return;
350 ScopedJavaLocalRef<jstring> jurl(
351 ConvertUTF8ToJavaString(env, expected_url.spec()));
352 ScopedJavaLocalRef<jstring> jpackage(
353 ConvertUTF8ToJavaString(env, tag_content));
354 Java_AppBannerManager_fetchAppDetails(env,
355 jobj.obj(),
356 jurl.obj(),
357 jpackage.obj(),
358 preferred_icon_size_);
361 bool AppBannerManager::OnAppDetailsRetrieved(JNIEnv* env,
362 jobject obj,
363 jobject japp_data,
364 jstring japp_title,
365 jstring japp_package,
366 jstring jicon_url) {
367 if (validated_url_ != web_contents()->GetURL())
368 return false;
370 std::string image_url = ConvertJavaStringToUTF8(env, jicon_url);
371 app_title_ = ConvertJavaStringToUTF16(env, japp_title);
372 native_app_package_ = ConvertJavaStringToUTF8(env, japp_package);
373 native_app_data_.Reset(env, japp_data);
374 return FetchIcon(GURL(image_url));
377 bool AppBannerManager::IsFetcherActive(JNIEnv* env, jobject obj) {
378 return fetcher_ != nullptr;
381 bool AppBannerManager::FetchIcon(const GURL& image_url) {
382 if (!web_contents())
383 return false;
385 // Begin asynchronously fetching the app icon.
386 Profile* profile =
387 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
389 CancelActiveFetcher();
390 fetcher_ = new BannerBitmapFetcher(image_url, this);
391 fetcher_->Start(
392 profile->GetRequestContext(),
393 std::string(),
394 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
395 net::LOAD_NORMAL);
396 app_icon_url_ = image_url;
397 return true;
400 // static
401 bool AppBannerManager::IsEnabledForNativeApps() {
402 return base::CommandLine::ForCurrentProcess()->HasSwitch(
403 switches::kEnableAppInstallAlerts);
406 // static
407 base::Time AppBannerManager::GetCurrentTime() {
408 return base::Time::Now() + gTimeDeltaForTesting;
411 jlong Init(JNIEnv* env, jobject obj, jint icon_size) {
412 AppBannerManager* manager = new AppBannerManager(env, obj, icon_size);
413 return reinterpret_cast<intptr_t>(manager);
416 jboolean IsEnabled(JNIEnv* env, jclass clazz) {
417 const std::string group_name =
418 base::FieldTrialList::FindFullName("AppBanners");
419 return group_name == "Enabled";
422 void SetTimeDeltaForTesting(JNIEnv* env, jclass clazz, jint days) {
423 gTimeDeltaForTesting = base::TimeDelta::FromDays(days);
426 void DisableSecureSchemeCheckForTesting(JNIEnv* env, jclass clazz) {
427 gDisableSecureCheckForTesting = true;
430 // Register native methods
431 bool RegisterAppBannerManager(JNIEnv* env) {
432 return RegisterNativesImpl(env);
435 } // namespace banners