blob: c08565526231580f4be23ebd5e0fe49c14028ffe [file] [log] [blame]
// Copyright 2014 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.
package org.chromium.chrome.browser.banners;
import android.app.PendingIntent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.text.TextUtils;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.chrome.browser.EmptyTabObserver;
import org.chromium.chrome.browser.Tab;
import org.chromium.chrome.browser.TabObserver;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.R;
/**
* Manages an AppBannerView for a Tab and its ContentView.
*
* The AppBannerManager manages a single AppBannerView, dismissing it when the user navigates to a
* new page or creating a new one when it detects that the current webpage is requesting a banner to
* be built. The actual observation of the WebContents (which triggers the automatic creation and
* removal of banners, among other things) is done by the native-side AppBannerManager.
*
* This Java-side class owns its native-side counterpart, which is basically used to grab resources
* from the network.
*/
@JNINamespace("banners")
public class AppBannerManager implements AppBannerView.Observer, AppDetailsDelegate.Observer {
private static final String TAG = "AppBannerManager";
/** Retrieves information about a given package. */
private static AppDetailsDelegate sAppDetailsDelegate;
/** Pointer to the native side AppBannerManager. */
private final long mNativePointer;
/** Tab that the AppBannerView/AppBannerManager is owned by. */
private final Tab mTab;
/** ContentViewCore that the AppBannerView/AppBannerManager is currently attached to. */
private ContentViewCore mContentViewCore;
/** Current banner being shown. */
private AppBannerView mBannerView;
/** Data about the app being advertised. */
private AppData mAppData;
/**
* Checks if app banners are enabled.
* @return True if banners are enabled, false otherwise.
*/
public static boolean isEnabled() {
return nativeIsEnabled();
}
/**
* Sets the delegate that provides information about a given package.
* @param delegate Delegate to use. Previously set ones are destroyed.
*/
public static void setAppDetailsDelegate(AppDetailsDelegate delegate) {
if (sAppDetailsDelegate != null) sAppDetailsDelegate.destroy();
sAppDetailsDelegate = delegate;
}
/**
* Constructs an AppBannerManager for the given tab.
* @param tab Tab that the AppBannerManager will be attached to.
*/
public AppBannerManager(Tab tab) {
mNativePointer = nativeInit();
mTab = tab;
mTab.addObserver(createTabObserver());
updatePointers();
}
/**
* Creates a TabObserver for monitoring a Tab, used to react to changes in the ContentView
* or to trigger its own destruction.
* @return TabObserver that can be used to monitor a Tab.
*/
private TabObserver createTabObserver() {
return new EmptyTabObserver() {
@Override
public void onWebContentsSwapped(Tab tab, boolean didStartLoad,
boolean didFinishLoad) {
updatePointers();
}
@Override
public void onContentChanged(Tab tab) {
updatePointers();
}
@Override
public void onDestroyed(Tab tab) {
nativeDestroy(mNativePointer);
mContentViewCore = null;
resetState();
}
};
}
/**
* Updates which ContentView and WebContents the AppBannerView is monitoring.
*/
private void updatePointers() {
if (mContentViewCore != mTab.getContentViewCore())
mContentViewCore = mTab.getContentViewCore();
nativeReplaceWebContents(mNativePointer, mTab.getWebContents());
}
/**
* Grabs package information for the banner asynchronously.
* @param url URL for the page that is triggering the banner.
* @param packageName Name of the package that is being advertised.
*/
@CalledByNative
private void prepareBanner(String url, String packageName) {
// Get rid of whatever banner is there currently.
if (mBannerView != null) dismissCurrentBanner(AppBannerMetricsIds.DISMISS_ERROR);
if (sAppDetailsDelegate == null || !isBannerForCurrentPage(url)) return;
int iconSize = AppBannerView.getIconSize(mContentViewCore.getContext());
sAppDetailsDelegate.getAppDetailsAsynchronously(this, url, packageName, iconSize);
}
/**
* Called when data about the package has been retrieved, which includes the url for the app's
* icon but not the icon Bitmap itself. Kicks off a background task to retrieve it.
* @param data Data about the app. Null if the task failed.
*/
@Override
public void onAppDetailsRetrieved(AppData data) {
if (data == null || !isBannerForCurrentPage(data.siteUrl())) return;
mAppData = data;
String imageUrl = data.imageUrl();
if (TextUtils.isEmpty(imageUrl) || !nativeFetchIcon(mNativePointer, imageUrl)) resetState();
}
/**
* Called when all the data required to show a banner has finally been retrieved.
* Creates the banner and shows it, as long as the banner is still meant for the current page.
* @param imageUrl URL of the icon.
* @param appIcon Bitmap containing the icon itself.
* @return Whether or not the banner was created.
*/
@CalledByNative
private boolean createBanner(String imageUrl, Bitmap appIcon) {
if (mAppData == null || !isBannerForCurrentPage(mAppData.siteUrl())) return false;
if (!TextUtils.equals(mAppData.imageUrl(), imageUrl)) {
resetState();
return false;
}
mAppData.setIcon(new BitmapDrawable(mContentViewCore.getContext().getResources(), appIcon));
mBannerView = AppBannerView.create(mContentViewCore, this, mAppData);
return true;
}
/**
* Dismisses whatever banner is currently being displayed. This is treated as an automatic
* dismissal and not one that blocks the banner from appearing in the future.
* @param dismissalType What triggered the dismissal.
*/
@CalledByNative
private void dismissCurrentBanner(int dismissalType) {
if (mBannerView != null) mBannerView.dismiss(dismissalType);
resetState();
}
@Override
public void onBannerRemoved(AppBannerView banner) {
if (mBannerView != banner) return;
resetState();
}
@Override
public void onBannerBlocked(AppBannerView banner, String url, String packageName) {
if (mBannerView != banner) return;
nativeBlockBanner(mNativePointer, url, packageName);
}
@Override
public void onBannerDismissEvent(AppBannerView banner, int eventType) {
if (mBannerView != banner) return;
nativeRecordDismissEvent(eventType);
}
@Override
public void onBannerInstallEvent(AppBannerView banner, int eventType) {
if (mBannerView != banner) return;
nativeRecordInstallEvent(eventType);
}
@Override
public boolean onFireIntent(AppBannerView banner, PendingIntent intent) {
if (mBannerView != banner) return false;
return mTab.getWindowAndroid().showIntent(intent, banner, R.string.low_memory_error);
}
/**
* Resets all of the state, killing off any running tasks.
*/
private void resetState() {
if (mBannerView != null) {
mBannerView.destroy();
mBannerView = null;
}
mAppData = null;
}
/**
* Checks to see if the banner is for the currently displayed page.
* @param bannerUrl URL that requested a banner.
* @return True if the user is still on the same page.
*/
private boolean isBannerForCurrentPage(String bannerUrl) {
return mContentViewCore != null &&
TextUtils.equals(mContentViewCore.getUrl(), bannerUrl);
}
private static native boolean nativeIsEnabled();
private native long nativeInit();
private native void nativeDestroy(long nativeAppBannerManager);
private native void nativeReplaceWebContents(long nativeAppBannerManager,
WebContents webContents);
private native void nativeBlockBanner(
long nativeAppBannerManager, String url, String packageName);
private native boolean nativeFetchIcon(long nativeAppBannerManager, String imageUrl);
// UMA tracking.
private static native void nativeRecordDismissEvent(int metric);
private static native void nativeRecordInstallEvent(int metric);
}