Commit 2220d25d authored by Dominick Ng's avatar Dominick Ng Committed by Commit Bot

Decouple app banner installation logic from UI on Android.

This CL extracts the UI-independent logic from
AppBannerInfoBarDelegateAndroid, and moves it to a new class,
AppBannerUiDelegateAndroid. This will allow a new modal UI surface to be
introduced for banners that uses the same app installation logic.

BUG=811578

Change-Id: I0998b0086783d4c78f25c966917c40adb9638f7f
Reviewed-on: https://chromium-review.googlesource.com/915442
Commit-Queue: Dominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537915}
parent 11608e6c
// Copyright 2018 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.os.Looper;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNIAdditionalImport;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.tab.Tab;
/**
* Handles the promotion and installation of an app specified by the current web page. This object
* is created by and owned by the native AppBannerUiDelegate.
*/
@JNINamespace("banners")
@JNIAdditionalImport(InstallerDelegate.class)
public class AppBannerUiDelegateAndroid {
/** Pointer to the native AppBannerUiDelegateAndroid. */
private long mNativePointer;
/** Delegate which does the actual monitoring of an in-progress installation. */
private InstallerDelegate mInstallerDelegate;
private AppBannerUiDelegateAndroid(long nativePtr) {
mNativePointer = nativePtr;
}
/**
* Creates the installer delegate with the specified observer. Must be called prior to using
* this object */
@CalledByNative
public void createInstallerDelegate(InstallerDelegate.Observer observer) {
mInstallerDelegate = new InstallerDelegate(Looper.getMainLooper(), observer);
}
@CalledByNative
private void destroy() {
mInstallerDelegate.destroy();
mInstallerDelegate = null;
mNativePointer = 0;
}
@CalledByNative
private boolean installOrOpenNativeApp(Tab tab, AppData appData, String referrer) {
return mInstallerDelegate.installOrOpenNativeApp(tab, appData, referrer);
}
@CalledByNative
private void showAppDetails(Tab tab, AppData appData) {
tab.getWindowAndroid().showIntent(appData.detailsIntent(), null, null);
}
@CalledByNative
private int determineInstallState(String packageName) {
return mInstallerDelegate.determineInstallState(packageName);
}
@CalledByNative
private static AppBannerUiDelegateAndroid create(long nativePtr) {
return new AppBannerUiDelegateAndroid(nativePtr);
}
}
......@@ -4,13 +4,9 @@
package org.chromium.chrome.browser.infobar;
import android.os.Looper;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.banners.AppData;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.tab.Tab;
/**
* Handles the promotion and installation of an app specified by the current web page. This object
......@@ -18,57 +14,33 @@ import org.chromium.chrome.browser.tab.Tab;
*/
@JNINamespace("banners")
public class AppBannerInfoBarDelegateAndroid implements InstallerDelegate.Observer {
/** Weak pointer to the native AppBannerInfoBarDelegateAndroid. */
/** Pointer to the native AppBannerInfoBarDelegateAndroid. */
private long mNativePointer;
/** Delegate which does the actual monitoring of an in-progress installation. */
private InstallerDelegate mInstallerDelegate;
private AppBannerInfoBarDelegateAndroid(long nativePtr) {
mNativePointer = nativePtr;
mInstallerDelegate = new InstallerDelegate(Looper.getMainLooper(), this);
}
@Override
public void onInstallIntentCompleted(InstallerDelegate delegate, boolean isInstalling) {
if (mInstallerDelegate != delegate) return;
nativeOnInstallIntentReturned(mNativePointer, isInstalling);
}
@Override
public void onInstallFinished(InstallerDelegate delegate, boolean success) {
if (mInstallerDelegate != delegate) return;
nativeOnInstallFinished(mNativePointer, success);
}
@Override
public void onApplicationStateChanged(InstallerDelegate delegate, int newState) {
if (mInstallerDelegate != delegate) return;
nativeUpdateInstallState(mNativePointer);
}
@CalledByNative
private void destroy() {
mInstallerDelegate.destroy();
mInstallerDelegate = null;
mNativePointer = 0;
}
@CalledByNative
private boolean installOrOpenNativeApp(Tab tab, AppData appData, String referrer) {
return mInstallerDelegate.installOrOpenNativeApp(tab, appData, referrer);
}
@CalledByNative
private void showAppDetails(Tab tab, AppData appData) {
tab.getWindowAndroid().showIntent(appData.detailsIntent(), null, null);
}
@CalledByNative
private int determineInstallState(String packageName) {
return mInstallerDelegate.determineInstallState(packageName);
}
@CalledByNative
private static AppBannerInfoBarDelegateAndroid create(long nativePtr) {
return new AppBannerInfoBarDelegateAndroid(nativePtr);
......
......@@ -97,6 +97,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
"java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
"java/src/org/chromium/chrome/browser/banners/AppData.java",
"java/src/org/chromium/chrome/browser/banners/AppDetailsDelegate.java",
"java/src/org/chromium/chrome/browser/banners/InstallerDelegate.java",
......
......@@ -2216,6 +2216,8 @@ jumbo_split_static_library("browser") {
"banners/app_banner_infobar_delegate_android.h",
"banners/app_banner_manager_android.cc",
"banners/app_banner_manager_android.h",
"banners/app_banner_ui_delegate_android.cc",
"banners/app_banner_ui_delegate_android.h",
"chrome_browser_field_trials_mobile.cc",
"chrome_browser_field_trials_mobile.h",
"dom_distiller/dom_distiller_service_factory_android.cc",
......@@ -4228,6 +4230,7 @@ if (is_android) {
"../android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java",
"../android/java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
"../android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
"../android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
"../android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
"../android/java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
"../android/java/src/org/chromium/chrome/browser/browsing_data/UrlFilterBridge.java",
......
......@@ -6,57 +6,32 @@
#define CHROME_BROWSER_BANNERS_APP_BANNER_INFOBAR_DELEGATE_ANDROID_H_
#include <memory>
#include <string>
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace content {
class WebContents;
}
namespace infobars {
class InfoBarManager;
}
enum class WebApkInstallResult;
struct ShortcutInfo;
namespace banners {
class AppBannerManager;
class AppBannerUiDelegateAndroid;
// Manages installation of an app being promoted by a page.
class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
public:
// Creates an infobar and delegate for promoting the installation of a web
// app, and adds the infobar to the InfoBarManager for |web_contents|.
// Creates an infobar and delegate for promoting the installation of an app,
// and displays the infobar for |web_contents|.
static bool Create(content::WebContents* web_contents,
base::WeakPtr<AppBannerManager> weak_manager,
std::unique_ptr<ShortcutInfo> info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
WebappInstallSource install_source,
bool is_webapk);
// Creates an infobar and delegate for promoting the installation of an
// Android app, and adds the infobar to the InfoBarManager for |web_contents|.
static bool Create(
content::WebContents* web_contents,
base::WeakPtr<AppBannerManager> weak_manager,
const base::string16& app_title,
const base::android::ScopedJavaGlobalRef<jobject>& native_app_data,
const SkBitmap& icon,
const std::string& native_app_package_name,
const std::string& referrer);
std::unique_ptr<AppBannerUiDelegateAndroid> ui_delegate);
~AppBannerInfoBarDelegateAndroid() override;
const AppBannerUiDelegateAndroid* GetUiDelegate() const;
// Called when the AppBannerInfoBarAndroid's button needs to be updated.
void UpdateInstallState(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
......@@ -72,43 +47,15 @@ class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
const base::android::JavaParamRef<jobject>& obj,
jboolean success);
const SkBitmap& GetPrimaryIcon() const;
// ConfirmInfoBarDelegate:
bool Accept() override;
base::string16 GetMessageText() const override;
private:
// Delegate for promoting a web app.
AppBannerInfoBarDelegateAndroid(base::WeakPtr<AppBannerManager> weak_manager,
std::unique_ptr<ShortcutInfo> info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
WebappInstallSource install_source,
bool is_webapk);
// Delegate for promoting an Android app.
AppBannerInfoBarDelegateAndroid(
base::WeakPtr<AppBannerManager> weak_manager,
const base::string16& app_title,
const base::android::ScopedJavaGlobalRef<jobject>& native_app_data,
const SkBitmap& icon,
const std::string& native_app_package_name,
const std::string& referrer);
void CreateJavaDelegate();
bool AcceptNativeApp(content::WebContents* web_contents);
bool AcceptWebApp(content::WebContents* web_contents);
// Called when the OK button on a WebAPK infobar is pressed. If the WebAPK is
// already installed, opens it; otherwise, installs it. Returns whether the
// infobar should be closed as a result of the button press.
bool AcceptWebApk(content::WebContents* web_contents);
// Called when the user accepts the banner to install the app. (Not called
// when the "Open" button is pressed on the banner that is shown after
// installation for WebAPK banners.)
void SendBannerAccepted();
std::unique_ptr<AppBannerUiDelegateAndroid> ui_delegate);
void CreateJavaDelegate(JNIEnv* env);
// ConfirmInfoBarDelegate:
infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
......@@ -116,26 +63,9 @@ class AppBannerInfoBarDelegateAndroid : public ConfirmInfoBarDelegate {
int GetButtons() const override;
bool LinkClicked(WindowOpenDisposition disposition) override;
std::unique_ptr<AppBannerUiDelegateAndroid> ui_delegate_;
base::android::ScopedJavaGlobalRef<jobject> java_delegate_;
// Used to fetch the splash screen icon for webapps.
base::WeakPtr<AppBannerManager> weak_manager_;
base::string16 app_title_;
std::unique_ptr<ShortcutInfo> shortcut_info_;
base::android::ScopedJavaGlobalRef<jobject> native_app_data_;
const SkBitmap primary_icon_;
const SkBitmap badge_icon_;
std::string package_name_;
std::string referrer_;
bool has_user_interaction_;
bool is_webapk_;
WebappInstallSource install_source_;
DISALLOW_COPY_AND_ASSIGN(AppBannerInfoBarDelegateAndroid);
};
......
......@@ -13,6 +13,7 @@
#include "chrome/browser/banners/app_banner_infobar_delegate_android.h"
#include "chrome/browser/banners/app_banner_metrics.h"
#include "chrome/browser/banners/app_banner_settings_helper.h"
#include "chrome/browser/banners/app_banner_ui_delegate_android.h"
#include "chrome/browser/installable/pwa_ambient_badge_manager_android.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/browser/web_contents.h"
......@@ -199,11 +200,14 @@ void AppBannerManagerAndroid::ShowBannerUi(WebappInstallSource install_source) {
DCHECK(contents);
if (native_app_data_.is_null()) {
if (AppBannerInfoBarDelegateAndroid::Create(
contents, GetWeakPtr(),
std::unique_ptr<AppBannerUiDelegateAndroid> ui_delegate =
AppBannerUiDelegateAndroid::Create(
GetWeakPtr(),
ShortcutHelper::CreateShortcutInfo(
manifest_url_, manifest_, primary_icon_url_, badge_icon_url_),
primary_icon_, badge_icon_, install_source, can_install_webapk_)) {
primary_icon_, badge_icon_, install_source, can_install_webapk_);
if (AppBannerInfoBarDelegateAndroid::Create(contents,
std::move(ui_delegate))) {
RecordDidShowBanner("AppBanner.WebApp.Shown");
TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_CREATED);
ReportStatus(SHOWING_WEB_APP_BANNER);
......@@ -211,9 +215,13 @@ void AppBannerManagerAndroid::ShowBannerUi(WebappInstallSource install_source) {
ReportStatus(FAILED_TO_CREATE_BANNER);
}
} else {
if (AppBannerInfoBarDelegateAndroid::Create(
contents, GetWeakPtr(), native_app_title_, native_app_data_,
primary_icon_, native_app_package_, referrer_)) {
std::unique_ptr<AppBannerUiDelegateAndroid> ui_delegate =
AppBannerUiDelegateAndroid::Create(
GetWeakPtr(), native_app_title_,
base::android::ScopedJavaLocalRef<jobject>(native_app_data_),
primary_icon_, native_app_package_, referrer_);
if (AppBannerInfoBarDelegateAndroid::Create(contents,
std::move(ui_delegate))) {
RecordDidShowBanner("AppBanner.NativeApp.Shown");
TrackDisplayEvent(DISPLAY_EVENT_NATIVE_APP_BANNER_CREATED);
ReportStatus(SHOWING_NATIVE_APP_BANNER);
......
This diff is collapsed.
// Copyright 2018 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.
#ifndef CHROME_BROWSER_BANNERS_APP_BANNER_UI_DELEGATE_ANDROID_H_
#define CHROME_BROWSER_BANNERS_APP_BANNER_UI_DELEGATE_ANDROID_H_
#include <memory>
#include <string>
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "chrome/browser/installable/installable_metrics.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace content {
class WebContents;
}
struct ShortcutInfo;
namespace banners {
class AppBannerManager;
// Delegate provided to the app banner UI surfaces to install a web app or
// native app.
class AppBannerUiDelegateAndroid {
public:
// Describes the type of app for which this class holds data.
enum class AppType {
NATIVE,
WEBAPK,
LEGACY_WEBAPP,
};
// Creates a delegate for promoting the installation of a web app.
static std::unique_ptr<AppBannerUiDelegateAndroid> Create(
base::WeakPtr<AppBannerManager> weak_manager,
std::unique_ptr<ShortcutInfo> info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
WebappInstallSource install_source,
bool is_webapk);
// Creates a delegate for promoting the installation of an Android app.
static std::unique_ptr<AppBannerUiDelegateAndroid> Create(
base::WeakPtr<AppBannerManager> weak_manager,
const base::string16& app_title,
const base::android::ScopedJavaLocalRef<jobject>& native_app_data,
const SkBitmap& icon,
const std::string& native_app_package_name,
const std::string& referrer);
~AppBannerUiDelegateAndroid();
const base::string16& GetAppTitle() const;
base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
const base::android::ScopedJavaLocalRef<jobject> GetNativeAppData() const;
int GetInstallState() const;
const SkBitmap& GetPrimaryIcon() const;
AppType GetType() const;
const GURL& GetWebAppUrl() const;
// Creates the Java-side InstallerDelegate, passing |jobserver| to receive
// progress updates on the installation of a native app.
void CreateInstallerDelegate(
base::android::ScopedJavaLocalRef<jobject> jobserver);
// Installs the app referenced by the data in this object. Returns |true| if
// the installation UI should be dismissed.
bool InstallApp(content::WebContents* web_contents);
// Called by the UI layer to indicate that a native app has begun
// installation.
void OnNativeAppInstallStarted(content::WebContents* web_contents);
// Called by the UI layer to indicate that a native app has finished
// installation.
void OnNativeAppInstallFinished(bool success);
// Called by the UI layer to indicate that the user has dismissed the
// installation UI.
void OnUiDismissed(content::WebContents* web_contents);
// Called by the UI layer to display the details for a native app.
void ShowNativeAppDetails(content::WebContents* web_contents);
private:
// Delegate for promoting a web app.
AppBannerUiDelegateAndroid(base::WeakPtr<AppBannerManager> weak_manager,
std::unique_ptr<ShortcutInfo> info,
const SkBitmap& primary_icon,
const SkBitmap& badge_icon,
WebappInstallSource install_source,
bool is_webapk);
// Delegate for promoting an Android app.
AppBannerUiDelegateAndroid(
base::WeakPtr<AppBannerManager> weak_manager,
const base::string16& app_title,
const base::android::ScopedJavaLocalRef<jobject>& native_app_data,
const SkBitmap& icon,
const std::string& native_app_package_name,
const std::string& referrer);
void CreateJavaDelegate();
bool InstallOrOpenNativeApp(content::WebContents* web_contents);
void InstallWebApk(content::WebContents* web_contents);
void InstallLegacyWebApp(content::WebContents* web_contents);
// Called when the user accepts the banner to install the app. (Not called
// when the "Open" button is pressed on the banner that is shown after
// installation for WebAPK banners.)
void SendBannerAccepted();
base::android::ScopedJavaGlobalRef<jobject> java_delegate_;
base::WeakPtr<AppBannerManager> weak_manager_;
base::string16 app_title_;
std::unique_ptr<ShortcutInfo> shortcut_info_;
base::android::ScopedJavaGlobalRef<jobject> native_app_data_;
const SkBitmap primary_icon_;
const SkBitmap badge_icon_;
std::string package_name_;
std::string referrer_;
AppType type_;
WebappInstallSource install_source_;
bool has_user_interaction_;
DISALLOW_COPY_AND_ASSIGN(AppBannerUiDelegateAndroid);
};
} // namespace banners
#endif // CHROME_BROWSER_BANNERS_APP_BANNER_UI_DELEGATE_ANDROID_H_
......@@ -4,53 +4,47 @@
#include "chrome/browser/ui/android/infobars/app_banner_infobar_android.h"
#include <memory>
#include <string>
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "chrome/browser/banners/app_banner_infobar_delegate_android.h"
#include "chrome/browser/banners/app_banner_ui_delegate_android.h"
#include "components/url_formatter/elide_url.h"
#include "jni/AppBannerInfoBarAndroid_jni.h"
#include "ui/gfx/android/java_bitmap.h"
AppBannerInfoBarAndroid::AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const base::android::ScopedJavaGlobalRef<jobject>& japp_data)
: ConfirmInfoBar(std::move(delegate)), japp_data_(japp_data) {}
AppBannerInfoBarAndroid::AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const GURL& app_url)
: ConfirmInfoBar(std::move(delegate)), app_url_(app_url) {}
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate)
: ConfirmInfoBar(std::move(delegate)) {}
AppBannerInfoBarAndroid::~AppBannerInfoBarAndroid() {}
base::android::ScopedJavaLocalRef<jobject>
AppBannerInfoBarAndroid::CreateRenderInfoBar(JNIEnv* env) {
banners::AppBannerInfoBarDelegateAndroid* delegate = GetDelegate();
const banners::AppBannerUiDelegateAndroid* delegate =
GetDelegate()->GetUiDelegate();
base::android::ScopedJavaLocalRef<jstring> app_title =
base::android::ConvertUTF16ToJavaString(env, delegate->GetMessageText());
base::android::ConvertUTF16ToJavaString(env, delegate->GetAppTitle());
DCHECK(!delegate->GetPrimaryIcon().drawsNothing());
base::android::ScopedJavaLocalRef<jobject> java_bitmap =
gfx::ConvertToJavaBitmap(&delegate->GetPrimaryIcon());
base::android::ScopedJavaLocalRef<jobject> infobar;
if (!japp_data_.is_null()) {
if (delegate->GetType() ==
banners::AppBannerUiDelegateAndroid::AppType::NATIVE) {
infobar.Reset(Java_AppBannerInfoBarAndroid_createNativeAppInfoBar(
env, app_title, java_bitmap, japp_data_));
env, app_title, java_bitmap, delegate->GetNativeAppData()));
} else {
// Trim down the app URL to the origin. Banners only show on secure origins,
// so elide the scheme.
base::android::ScopedJavaLocalRef<jstring> app_url =
base::android::ConvertUTF16ToJavaString(
env,
url_formatter::FormatUrlForSecurityDisplay(
app_url_, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
env, url_formatter::FormatUrlForSecurityDisplay(
delegate->GetWebAppUrl(),
url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
infobar.Reset(Java_AppBannerInfoBarAndroid_createWebAppInfoBar(
env, app_title, java_bitmap, app_url));
......
......@@ -5,10 +5,11 @@
#ifndef CHROME_BROWSER_UI_ANDROID_INFOBARS_APP_BANNER_INFOBAR_ANDROID_H_
#define CHROME_BROWSER_UI_ANDROID_INFOBARS_APP_BANNER_INFOBAR_ANDROID_H_
#include <memory>
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "chrome/browser/ui/android/infobars/confirm_infobar.h"
#include "url/gurl.h"
namespace banners {
class AppBannerInfoBarDelegateAndroid;
......@@ -17,15 +18,8 @@ class AppBannerInfoBarDelegateAndroid;
class AppBannerInfoBarAndroid : public ConfirmInfoBar {
public:
// Constructs an AppBannerInfoBarAndroid promoting a native app.
AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const base::android::ScopedJavaGlobalRef<jobject>& japp_data);
// Constructs an AppBannerInfoBarAndroid promoting a web app.
AppBannerInfoBarAndroid(
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate,
const GURL& app_url);
std::unique_ptr<banners::AppBannerInfoBarDelegateAndroid> delegate);
~AppBannerInfoBarAndroid() override;
......@@ -41,12 +35,6 @@ class AppBannerInfoBarAndroid : public ConfirmInfoBar {
base::android::ScopedJavaLocalRef<jobject> CreateRenderInfoBar(
JNIEnv* env) override;
// Native app: Details about the app.
base::android::ScopedJavaGlobalRef<jobject> japp_data_;
// Web app: URL for the app.
GURL app_url_;
base::android::ScopedJavaGlobalRef<jobject> java_infobar_;
DISALLOW_COPY_AND_ASSIGN(AppBannerInfoBarAndroid);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment