// 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); }