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. 4 5package org.chromium.chrome.browser.banners; 6 7import android.app.PendingIntent; 8import android.graphics.Bitmap; 9import android.graphics.drawable.BitmapDrawable; 10import android.text.TextUtils; 11 12import org.chromium.base.CalledByNative; 13import org.chromium.base.JNINamespace; 14import org.chromium.chrome.browser.EmptyTabObserver; 15import org.chromium.chrome.browser.Tab; 16import org.chromium.chrome.browser.TabObserver; 17import org.chromium.content.browser.ContentViewCore; 18import org.chromium.content_public.browser.WebContents; 19import org.chromium.ui.R; 20 21/** 22 * Manages an AppBannerView for a Tab and its ContentView. 23 * 24 * The AppBannerManager manages a single AppBannerView, dismissing it when the user navigates to a 25 * new page or creating a new one when it detects that the current webpage is requesting a banner to 26 * be built. The actual observation of the WebContents (which triggers the automatic creation and 27 * removal of banners, among other things) is done by the native-side AppBannerManager. 28 * 29 * This Java-side class owns its native-side counterpart, which is basically used to grab resources 30 * from the network. 31 */ 32@JNINamespace("banners") 33public class AppBannerManager implements AppBannerView.Observer, AppDetailsDelegate.Observer { 34 private static final String TAG = "AppBannerManager"; 35 36 /** Retrieves information about a given package. */ 37 private static AppDetailsDelegate sAppDetailsDelegate; 38 39 /** Pointer to the native side AppBannerManager. */ 40 private final long mNativePointer; 41 42 /** Tab that the AppBannerView/AppBannerManager is owned by. */ 43 private final Tab mTab; 44 45 /** ContentViewCore that the AppBannerView/AppBannerManager is currently attached to. */ 46 private ContentViewCore mContentViewCore; 47 48 /** Current banner being shown. */ 49 private AppBannerView mBannerView; 50 51 /** Data about the app being advertised. */ 52 private AppData mAppData; 53 54 /** 55 * Checks if app banners are enabled. 56 * @return True if banners are enabled, false otherwise. 57 */ 58 public static boolean isEnabled() { 59 return nativeIsEnabled(); 60 } 61 62 /** 63 * Sets the delegate that provides information about a given package. 64 * @param delegate Delegate to use. Previously set ones are destroyed. 65 */ 66 public static void setAppDetailsDelegate(AppDetailsDelegate delegate) { 67 if (sAppDetailsDelegate != null) sAppDetailsDelegate.destroy(); 68 sAppDetailsDelegate = delegate; 69 } 70 71 /** 72 * Constructs an AppBannerManager for the given tab. 73 * @param tab Tab that the AppBannerManager will be attached to. 74 */ 75 public AppBannerManager(Tab tab) { 76 mNativePointer = nativeInit(); 77 mTab = tab; 78 mTab.addObserver(createTabObserver()); 79 updatePointers(); 80 } 81 82 /** 83 * Creates a TabObserver for monitoring a Tab, used to react to changes in the ContentView 84 * or to trigger its own destruction. 85 * @return TabObserver that can be used to monitor a Tab. 86 */ 87 private TabObserver createTabObserver() { 88 return new EmptyTabObserver() { 89 @Override 90 public void onWebContentsSwapped(Tab tab, boolean didStartLoad, 91 boolean didFinishLoad) { 92 updatePointers(); 93 } 94 95 @Override 96 public void onContentChanged(Tab tab) { 97 updatePointers(); 98 } 99 100 @Override 101 public void onDestroyed(Tab tab) { 102 nativeDestroy(mNativePointer); 103 mContentViewCore = null; 104 resetState(); 105 } 106 }; 107 } 108 109 /** 110 * Updates which ContentView and WebContents the AppBannerView is monitoring. 111 */ 112 private void updatePointers() { 113 if (mContentViewCore != mTab.getContentViewCore()) 114 mContentViewCore = mTab.getContentViewCore(); 115 nativeReplaceWebContents(mNativePointer, mTab.getWebContents()); 116 } 117 118 /** 119 * Grabs package information for the banner asynchronously. 120 * @param url URL for the page that is triggering the banner. 121 * @param packageName Name of the package that is being advertised. 122 */ 123 @CalledByNative 124 private void prepareBanner(String url, String packageName) { 125 // Get rid of whatever banner is there currently. 126 if (mBannerView != null) dismissCurrentBanner(AppBannerMetricsIds.DISMISS_ERROR); 127 128 if (sAppDetailsDelegate == null || !isBannerForCurrentPage(url)) return; 129 130 int iconSize = AppBannerView.getIconSize(mContentViewCore.getContext()); 131 sAppDetailsDelegate.getAppDetailsAsynchronously(this, url, packageName, iconSize); 132 } 133 134 /** 135 * Called when data about the package has been retrieved, which includes the url for the app's 136 * icon but not the icon Bitmap itself. Kicks off a background task to retrieve it. 137 * @param data Data about the app. Null if the task failed. 138 */ 139 @Override 140 public void onAppDetailsRetrieved(AppData data) { 141 if (data == null || !isBannerForCurrentPage(data.siteUrl())) return; 142 143 mAppData = data; 144 String imageUrl = data.imageUrl(); 145 if (TextUtils.isEmpty(imageUrl) || !nativeFetchIcon(mNativePointer, imageUrl)) resetState(); 146 } 147 148 /** 149 * Called when all the data required to show a banner has finally been retrieved. 150 * Creates the banner and shows it, as long as the banner is still meant for the current page. 151 * @param imageUrl URL of the icon. 152 * @param appIcon Bitmap containing the icon itself. 153 * @return Whether or not the banner was created. 154 */ 155 @CalledByNative 156 private boolean createBanner(String imageUrl, Bitmap appIcon) { 157 if (mAppData == null || !isBannerForCurrentPage(mAppData.siteUrl())) return false; 158 159 if (!TextUtils.equals(mAppData.imageUrl(), imageUrl)) { 160 resetState(); 161 return false; 162 } 163 164 mAppData.setIcon(new BitmapDrawable(mContentViewCore.getContext().getResources(), appIcon)); 165 mBannerView = AppBannerView.create(mContentViewCore, this, mAppData); 166 return true; 167 } 168 169 /** 170 * Dismisses whatever banner is currently being displayed. This is treated as an automatic 171 * dismissal and not one that blocks the banner from appearing in the future. 172 * @param dismissalType What triggered the dismissal. 173 */ 174 @CalledByNative 175 private void dismissCurrentBanner(int dismissalType) { 176 if (mBannerView != null) mBannerView.dismiss(dismissalType); 177 resetState(); 178 } 179 180 @Override 181 public void onBannerRemoved(AppBannerView banner) { 182 if (mBannerView != banner) return; 183 resetState(); 184 } 185 186 @Override 187 public void onBannerBlocked(AppBannerView banner, String url, String packageName) { 188 if (mBannerView != banner) return; 189 nativeBlockBanner(mNativePointer, url, packageName); 190 } 191 192 @Override 193 public void onBannerDismissEvent(AppBannerView banner, int eventType) { 194 if (mBannerView != banner) return; 195 nativeRecordDismissEvent(eventType); 196 } 197 198 @Override 199 public void onBannerInstallEvent(AppBannerView banner, int eventType) { 200 if (mBannerView != banner) return; 201 nativeRecordInstallEvent(eventType); 202 } 203 204 @Override 205 public boolean onFireIntent(AppBannerView banner, PendingIntent intent) { 206 if (mBannerView != banner) return false; 207 return mTab.getWindowAndroid().showIntent(intent, banner, R.string.low_memory_error); 208 } 209 210 /** 211 * Resets all of the state, killing off any running tasks. 212 */ 213 private void resetState() { 214 if (mBannerView != null) { 215 mBannerView.destroy(); 216 mBannerView = null; 217 } 218 219 mAppData = null; 220 } 221 222 /** 223 * Checks to see if the banner is for the currently displayed page. 224 * @param bannerUrl URL that requested a banner. 225 * @return True if the user is still on the same page. 226 */ 227 private boolean isBannerForCurrentPage(String bannerUrl) { 228 return mContentViewCore != null && 229 TextUtils.equals(mContentViewCore.getWebContents().getUrl(), bannerUrl); 230 } 231 232 private static native boolean nativeIsEnabled(); 233 private native long nativeInit(); 234 private native void nativeDestroy(long nativeAppBannerManager); 235 private native void nativeReplaceWebContents(long nativeAppBannerManager, 236 WebContents webContents); 237 private native void nativeBlockBanner( 238 long nativeAppBannerManager, String url, String packageName); 239 private native boolean nativeFetchIcon(long nativeAppBannerManager, String imageUrl); 240 241 // UMA tracking. 242 private static native void nativeRecordDismissEvent(int metric); 243 private static native void nativeRecordInstallEvent(int metric); 244} 245