// Copyright 2015 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.infobar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Looper;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.banners.AppData;
import org.chromium.chrome.browser.banners.InstallerDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.widget.Toast;
/**
* Handles the promotion and installation of an app specified by the current web page. This Java
* object is created by and owned by the native AppBannerInfoBarDelegateAndroid.
*/
@JNINamespace("banners")
public class AppBannerInfoBarDelegateAndroid {
private static final String TAG = "cr_AppBannerInfoBar";
/** PackageManager to use in place of the real one. */
private static PackageManager sPackageManagerForTests;
/** Weak pointer to the native AppBannerInfoBarDelegateAndroid. */
private long mNativePointer;
/** Monitors an installation in progress. */
private InstallerDelegate mInstallTask;
/** Monitors for application state changes. */
private final ApplicationStatus.ApplicationStateListener mListener;
/**
* Indicates whether a request to install a WebAPK has started. This flag is set while the
* WebAPK is being installed.
*/
private boolean mIsInstallingWebApk;
/** The package name of the WebAPK. */
private String mWebApkPackage;
/** Overrides the PackageManager for testing. */
@VisibleForTesting
public static void setPackageManagerForTesting(PackageManager manager) {
sPackageManagerForTests = manager;
}
private AppBannerInfoBarDelegateAndroid(long nativePtr) {
mNativePointer = nativePtr;
mListener = createApplicationStateListener();
ApplicationStatus.registerApplicationStateListener(mListener);
}
private ApplicationStatus.ApplicationStateListener createApplicationStateListener() {
return new ApplicationStatus.ApplicationStateListener() {
@Override
public void onApplicationStateChange(int newState) {
if (!ApplicationStatus.hasVisibleActivities()) return;
nativeUpdateInstallState(mNativePointer);
}
};
}
@CalledByNative
private void destroy() {
if (mInstallTask != null) {
mInstallTask.cancel();
mInstallTask = null;
}
ApplicationStatus.unregisterApplicationStateListener(mListener);
mNativePointer = 0;
}
@CalledByNative
private boolean installOrOpenNativeApp(Tab tab, AppData appData, String referrer) {
Context context = ContextUtils.getApplicationContext();
String packageName = appData.packageName();
PackageManager packageManager = getPackageManager(context);
if (InstallerDelegate.isInstalled(packageManager, packageName)) {
// Open the app.
openApp(context, packageName);
return true;
} else {
// Try installing the app. If the installation was kicked off, return false to prevent
// the infobar from disappearing.
// The supplied referrer is the URL of the page requesting the native app banner. It
// may be empty depending on that page's referrer policy. If it is non-empty, attach it
// to the installation intent as Intent.EXTRA_REFERRER.
Intent installIntent = appData.installIntent();
if (referrer.length() > 0) installIntent.putExtra(Intent.EXTRA_REFERRER, referrer);
return !tab.getWindowAndroid().showIntent(
installIntent, createIntentCallback(appData), null);
}
}
void openApp(Context context, String packageName) {
Intent launchIntent = getPackageManager(context).getLaunchIntentForPackage(packageName);
if (launchIntent != null) {
try {
context.startActivity(launchIntent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open app : %s!", packageName, e);
return;
}
}
}
private WindowAndroid.IntentCallback createIntentCallback(final AppData appData) {
return new WindowAndroid.IntentCallback() {
@Override
public void onIntentCompleted(WindowAndroid window, int resultCode,
ContentResolver contentResolver, Intent data) {
boolean isInstalling = resultCode == Activity.RESULT_OK;
if (isInstalling) {
// Start monitoring the install.
PackageManager pm =
getPackageManager(ContextUtils.getApplicationContext());
mInstallTask = new InstallerDelegate(
Looper.getMainLooper(), pm, createInstallerDelegateObserver(),
appData.packageName());
mInstallTask.start();
}
nativeOnInstallIntentReturned(mNativePointer, isInstalling);
}
};
}
private InstallerDelegate.Observer createInstallerDelegateObserver() {
return new InstallerDelegate.Observer() {
@Override
public void onInstallFinished(InstallerDelegate task, boolean success) {
if (mInstallTask != task) return;
mInstallTask = null;
nativeOnInstallFinished(mNativePointer, success);
}
};
}
@CalledByNative
private void openWebApk(String packageName) {
Context context = ContextUtils.getApplicationContext();
PackageManager packageManager = getPackageManager(context);
if (InstallerDelegate.isInstalled(packageManager, packageName)) {
mWebApkPackage = null;
openApp(context, packageName);
}
}
@CalledByNative
private void showAppDetails(Tab tab, AppData appData) {
tab.getWindowAndroid().showIntent(appData.detailsIntent(), null, null);
}
@CalledByNative
private int determineInstallState(AppData data) {
if (mInstallTask != null || mIsInstallingWebApk) {
return AppBannerInfoBarAndroid.INSTALL_STATE_INSTALLING;
}
PackageManager pm = getPackageManager(ContextUtils.getApplicationContext());
String packageName = (data != null) ? data.packageName() : mWebApkPackage;
boolean isInstalled = InstallerDelegate.isInstalled(pm, packageName);
return isInstalled ? AppBannerInfoBarAndroid.INSTALL_STATE_INSTALLED
: AppBannerInfoBarAndroid.INSTALL_STATE_NOT_INSTALLED;
}
@CalledByNative
/** Set the flag of whether the installation process has been started for the WebAPK. */
private void setWebApkInstallingState(boolean isInstalling) {
mIsInstallingWebApk = isInstalling;
}
@CalledByNative
/** Sets the WebAPK package name. */
private void setWebApkPackageName(String webApkPackage) {
mWebApkPackage = webApkPackage;
}
@CalledByNative
private static void showWebApkInstallFailureToast() {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
Context applicationContext = ContextUtils.getApplicationContext();
Toast toast = Toast.makeText(applicationContext, R.string.fail_to_install_webapk,
Toast.LENGTH_SHORT);
toast.show();
}
});
}
private PackageManager getPackageManager(Context context) {
if (sPackageManagerForTests != null) return sPackageManagerForTests;
return context.getPackageManager();
}
@CalledByNative
private static AppBannerInfoBarDelegateAndroid create(long nativePtr) {
return new AppBannerInfoBarDelegateAndroid(nativePtr);
}
private native void nativeOnInstallIntentReturned(
long nativeAppBannerInfoBarDelegateAndroid, boolean isInstalling);
private native void nativeOnInstallFinished(
long nativeAppBannerInfoBarDelegateAndroid, boolean success);
private native void nativeUpdateInstallState(long nativeAppBannerInfoBarDelegateAndroid);
}