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"
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
;
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"))
60 for (const auto& size
: icon
.sizes
) {
61 if (size
.IsEmpty()) // "any"
63 if (size
.width() >= kIconMinimumSize
&& size
.height() >= kIconMinimumSize
)
71 } // anonymous namespace
75 // Fetches a bitmap and deletes itself when completed.
76 class AppBannerManager::BannerBitmapFetcher
77 : public chrome::BitmapFetcher
,
78 public chrome::BitmapFetcherDelegate
{
80 BannerBitmapFetcher(const GURL
& image_url
,
81 banners::AppBannerManager
* manager
);
83 // Prevents informing the AppBannerManager that the fetch has completed.
86 // chrome::BitmapFetcherDelegate overrides
87 void OnFetchComplete(const GURL url
, const SkBitmap
* icon
) override
;
90 banners::AppBannerManager
* manager_
;
94 AppBannerManager::BannerBitmapFetcher::BannerBitmapFetcher(
95 const GURL
& image_url
,
96 banners::AppBannerManager
* manager
)
97 : chrome::BitmapFetcher(image_url
, this),
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(
109 const SkBitmap
* icon
) {
110 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
113 manager_
->OnFetchComplete(this, url
, icon
);
118 AppBannerManager::AppBannerManager(JNIEnv
* env
, jobject obj
, int icon_size
)
119 : preferred_icon_size_(icon_size
),
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
) {
133 void AppBannerManager::ReplaceWebContents(JNIEnv
* env
,
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())
157 validated_url_
= validated_url
;
159 // A secure scheme is required to show banners, so exit early if we see the
161 if (!validated_url_
.SchemeIsSecure() && !gDisableSecureCheckForTesting
)
164 // See if the page has a manifest.
165 web_contents()->GetManifest(base::Bind(&AppBannerManager::OnDidGetManifest
,
166 weak_factory_
.GetWeakPtr()));
170 bool AppBannerManager::IsManifestValid(const content::Manifest
& manifest
) {
171 if (manifest
.IsEmpty())
173 if (!manifest
.start_url
.is_valid())
175 if (manifest
.name
.is_null() && manifest
.short_name
.is_null())
177 if (!DoesManifestContainRequiredIcon(manifest
))
183 void AppBannerManager::OnDidGetManifest(const content::Manifest
& manifest
) {
184 if (web_contents()->IsBeingDestroyed())
187 if (!IsManifestValid(manifest
)) {
188 // No usable manifest, see if there is a play store meta tag.
189 if (!IsEnabledForNativeApps())
192 Send(new ChromeViewMsg_RetrieveMetaTagContent(routing_id(),
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.
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.
223 ManifestIconSelector::FindBestMatchingIcon(
225 preferred_icon_size_
,
226 gfx::Screen::GetScreenFor(web_contents()->GetNativeView()));
227 if (icon_url
.is_empty())
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
,
251 AppBannerSettingsHelper::RecordBannerEvent(
252 web_contents(), validated_url_
, package_or_start_url
,
253 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW
, GetCurrentTime());
257 void AppBannerManager::CancelActiveFetcher() {
258 // Fetchers delete themselves.
259 if (fetcher_
!= nullptr) {
265 bool AppBannerManager::OnMessageReceived(const IPC::Message
& message
) {
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()
275 void AppBannerManager::OnFetchComplete(BannerBitmapFetcher
* fetcher
,
277 const SkBitmap
* bitmap
) {
278 if (fetcher_
!= fetcher
)
283 || web_contents()->IsBeingDestroyed()
284 || validated_url_
!= web_contents()->GetURL()
286 || url
!= app_icon_url_
) {
290 JNIEnv
* env
= base::android::AttachCurrentThread();
291 ScopedJavaLocalRef
<jobject
> jobj
= weak_java_banner_view_manager_
.get(env
);
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_
))
303 weak_infobar_ptr
= AppBannerInfoBarDelegate::CreateForNativeApp(
306 new SkBitmap(*bitmap
),
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()))
317 weak_infobar_ptr
= AppBannerInfoBarDelegate::CreateForWebApp(
320 new SkBitmap(*bitmap
),
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(
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
) {
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
);
350 ScopedJavaLocalRef
<jstring
> jurl(
351 ConvertUTF8ToJavaString(env
, expected_url
.spec()));
352 ScopedJavaLocalRef
<jstring
> jpackage(
353 ConvertUTF8ToJavaString(env
, tag_content
));
354 Java_AppBannerManager_fetchAppDetails(env
,
358 preferred_icon_size_
);
361 bool AppBannerManager::OnAppDetailsRetrieved(JNIEnv
* env
,
365 jstring japp_package
,
367 if (validated_url_
!= web_contents()->GetURL())
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
) {
385 // Begin asynchronously fetching the app icon.
387 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
389 CancelActiveFetcher();
390 fetcher_
= new BannerBitmapFetcher(image_url
, this);
392 profile
->GetRequestContext(),
394 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE
,
396 app_icon_url_
= image_url
;
401 bool AppBannerManager::IsEnabledForNativeApps() {
402 return base::CommandLine::ForCurrentProcess()->HasSwitch(
403 switches::kEnableAppInstallAlerts
);
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