chromium/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchQuickActionControl.java

// Copyright 2016 The Chromium Authors
// 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.compositor.bottombar.contextualsearch;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.provider.Browser;
import android.text.TextUtils;
import android.widget.ImageView;

import androidx.core.graphics.drawable.DrawableCompat;

import org.chromium.base.IntentUtils;
import org.chromium.base.PackageManagerUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity2;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma;
import org.chromium.chrome.browser.contextualsearch.QuickActionCategory;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
import org.chromium.ui.resources.dynamics.ViewResourceInflater;
import org.chromium.ui.util.ColorUtils;

import java.net.URISyntaxException;
import java.util.List;

/**
 * Stores information related to a Contextual Search "quick action."
 * Actions can be activated through a tap on the Bar and include intents like calling a phone
 * number or launching Maps for a street address.
 */
public class ContextualSearchQuickActionControl extends ViewResourceInflater {
    private Context mContext;
    private String mQuickActionUri;
    private int mQuickActionCategory;
    private int mToolbarBackgroundColor;
    private boolean mHasQuickAction;
    private boolean mOpenQuickActionInChrome;
    private Intent mIntent;
    private String mCaption;

    /**
     * @param context The Android Context used to inflate the View.
     * @param resourceLoader The resource loader that will handle the snapshot capturing.
     */
    public ContextualSearchQuickActionControl(
            Context context, DynamicResourceLoader resourceLoader) {
        super(
                R.layout.contextual_search_quick_action_icon_view,
                R.id.contextual_search_quick_action_icon_view,
                context,
                null,
                resourceLoader);
        mContext = context;
    }

    /**
     * Gets the resource ID of the icon for the given category.
     * @param category Which application category the icon should be for.
     * @return         The resource ID or {@code null} if an unsupported category is supplied.
     */
    private static Integer getIconResId(@QuickActionCategory int category) {
        switch (category) {
            case QuickActionCategory.ADDRESS:
                return R.drawable.ic_place_googblue_36dp;
            case QuickActionCategory.EMAIL:
                return R.drawable.ic_email_googblue_36dp;
            case QuickActionCategory.EVENT:
                return R.drawable.ic_event_googblue_36dp;
            case QuickActionCategory.PHONE:
                return R.drawable.ic_phone_googblue_36dp;
            case QuickActionCategory.WEBSITE:
                return R.drawable.ic_link_grey600_36dp;
            default:
                return null;
        }
    }

    /**
     * Gets the caption string to show for the default app for the given category.
     * @param  category Which application category the string should be for.
     * @return          A string ID or {@code null} if an unsupported category is supplied.
     */
    private static Integer getDefaultAppCaptionId(@QuickActionCategory int category) {
        switch (category) {
            case QuickActionCategory.ADDRESS:
                return R.string.contextual_search_quick_action_caption_open;
            case QuickActionCategory.EMAIL:
                return R.string.contextual_search_quick_action_caption_email;
            case QuickActionCategory.EVENT:
                return R.string.contextual_search_quick_action_caption_event;
            case QuickActionCategory.PHONE:
                return R.string.contextual_search_quick_action_caption_phone;
            case QuickActionCategory.WEBSITE:
                return R.string.contextual_search_quick_action_caption_open;
            default:
                return null;
        }
    }

    /**
     * Gets the caption string to show for a generic app of the given category.
     * @param  category Which application category the string should be for.
     * @return          A string ID or {@code null} if an unsupported category is supplied.
     */
    private static Integer getFallbackCaptionId(@QuickActionCategory int category) {
        switch (category) {
            case QuickActionCategory.ADDRESS:
                return R.string.contextual_search_quick_action_caption_generic_map;
            case QuickActionCategory.EMAIL:
                return R.string.contextual_search_quick_action_caption_generic_email;
            case QuickActionCategory.EVENT:
                return R.string.contextual_search_quick_action_caption_generic_event;
            case QuickActionCategory.PHONE:
                return R.string.contextual_search_quick_action_caption_phone;
            case QuickActionCategory.WEBSITE:
                return R.string.contextual_search_quick_action_caption_generic_website;
            default:
                return null;
        }
    }

    /**
     * @param quickActionUri         The URI for the intent associated with the quick action.
     *                               If the URI is the empty string or cannot be parsed no quick
     *                               action will be available.
     * @param quickActionCategory    The {@link QuickActionCategory} for the quick action.
     * @param toolbarBackgroundColor The current toolbar background color. This may be
     *                               used for icon tinting.
     */
    public void setQuickAction(
            String quickActionUri, int quickActionCategory, int toolbarBackgroundColor) {
        if (TextUtils.isEmpty(quickActionUri)
                || quickActionCategory == QuickActionCategory.NONE
                || quickActionCategory >= QuickActionCategory.BOUNDARY) {
            reset();
            return;
        }

        mQuickActionUri = quickActionUri;
        mQuickActionCategory = quickActionCategory;
        mToolbarBackgroundColor = toolbarBackgroundColor;

        resolveIntent();
    }

    /**
     * Sends the intent associated with the quick action if one is available.
     * @param tab The current tab, used to load a URL if the quick action should open
     *            inside Chrome.
     */
    public void sendIntent(Tab tab) {
        if (mOpenQuickActionInChrome) {
            tab.loadUrl(new LoadUrlParams(mQuickActionUri));
            return;
        }

        if (mIntent == null) return;

        // Set the Browser application ID to us in case the user chooses Chrome
        // as the app from the intent picker.
        Context context = getContext();
        mIntent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());

        mIntent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
        mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        if (context instanceof ChromeTabbedActivity2) {
            // Set the window ID so the new tab opens in the correct window.
            mIntent.putExtra(IntentHandler.EXTRA_WINDOW_ID, 2);
        }

        IntentUtils.safeStartActivity(mContext, mIntent);
    }

    /**
     * @return The caption associated with the quick action or null if no quick action
     *         is available.
     */
    public String getCaption() {
        return mCaption;
    }

    /**
     * @return The resource id for the icon associated with the quick action or 0 if no
     *         quick action is available.
     */
    public int getIconResId() {
        return mHasQuickAction ? getViewId() : 0;
    }

    /**
     * @return Whether there is currently a quick action available.
     */
    public boolean hasQuickAction() {
        return mHasQuickAction;
    }

    /** Resets quick action data. */
    public void reset() {
        mQuickActionUri = "";
        mQuickActionCategory = QuickActionCategory.NONE;
        mHasQuickAction = false;
        mOpenQuickActionInChrome = false;
        mIntent = null;
        mCaption = "";
        mToolbarBackgroundColor = 0;
    }

    @Override
    protected boolean shouldAttachView() {
        return false;
    }

    private void resolveIntent() {
        try {
            mIntent = Intent.parseUri(mQuickActionUri, 0);
        } catch (URISyntaxException e) {
            // If the intent cannot be parsed, there is no quick action available.
            ContextualSearchUma.logQuickActionIntentResolution(mQuickActionCategory, 0);
            reset();
            return;
        }

        PackageManager packageManager = mContext.getPackageManager();

        // If a default is set, PackageManager#resolveActivity() will return the
        // ResolveInfo for the default activity.
        ResolveInfo possibleDefaultActivity = PackageManagerUtils.resolveActivity(mIntent, 0);

        // PackageManager#queryIntentActivities() will return a list of activities that
        // can handle the intent, sorted from best to worst. If there are no matching
        // activities, an empty list is returned.
        List<ResolveInfo> resolveInfoList = PackageManagerUtils.queryIntentActivities(mIntent, 0);

        int numMatchingActivities = 0;
        ResolveInfo defaultActivityResolveInfo = null;
        for (ResolveInfo resolveInfo : resolveInfoList) {
            if (resolveInfo.activityInfo != null && resolveInfo.activityInfo.exported) {
                numMatchingActivities++;
                if (possibleDefaultActivity == null
                        || possibleDefaultActivity.activityInfo == null) {
                    continue;
                }

                // Return early if this resolveInfo matches the possibleDefaultActivity.
                ActivityInfo possibleDefaultActivityInfo = possibleDefaultActivity.activityInfo;
                ActivityInfo resolveActivityInfo = resolveInfo.activityInfo;
                boolean matchesPossibleDefaultActivity =
                        TextUtils.equals(resolveActivityInfo.name, possibleDefaultActivityInfo.name)
                                && TextUtils.equals(
                                        resolveActivityInfo.packageName,
                                        possibleDefaultActivityInfo.packageName);

                if (matchesPossibleDefaultActivity) {
                    defaultActivityResolveInfo = resolveInfo;
                    break;
                }
            }
        }

        ContextualSearchUma.logQuickActionIntentResolution(
                mQuickActionCategory, numMatchingActivities);

        if (numMatchingActivities == 0) {
            reset();
            return;
        }

        mHasQuickAction = true;
        Drawable iconDrawable = null;
        int iconResId = 0;
        if (defaultActivityResolveInfo != null) {
            iconDrawable = defaultActivityResolveInfo.loadIcon(mContext.getPackageManager());

            if (mQuickActionCategory != QuickActionCategory.PHONE) {
                // Use the default app's name to construct the caption.
                mCaption =
                        mContext.getResources()
                                .getString(
                                        getDefaultAppCaptionId(mQuickActionCategory),
                                        defaultActivityResolveInfo.loadLabel(packageManager));
            } else {
                // The caption for phone numbers does not use the app's name.
                mCaption =
                        mContext.getResources()
                                .getString(getDefaultAppCaptionId(mQuickActionCategory));
            }
        } else if (mQuickActionCategory == QuickActionCategory.WEBSITE) {
            // If there is not a default app handler for a URL, open the quick action
            // inside of Chrome.
            mOpenQuickActionInChrome = true;

            if (mContext instanceof ChromeTabbedActivity) {
                // Use the app icon if this is a ChromeTabbedActivity instance.
                iconResId = R.mipmap.app_icon;
            } else {
                // Otherwise use the link icon.
                iconResId = getIconResId(mQuickActionCategory);

                if (mToolbarBackgroundColor != 0
                        && !ThemeUtils.isUsingDefaultToolbarColor(
                                mContext, false, mToolbarBackgroundColor)
                        && ColorUtils.shouldUseLightForegroundOnBackground(
                                mToolbarBackgroundColor)) {
                    // Tint the link icon to match the custom tab toolbar.
                    iconDrawable = mContext.getDrawable(iconResId);
                    iconDrawable.mutate();
                    DrawableCompat.setTint(iconDrawable, mToolbarBackgroundColor);
                }
            }
            mCaption =
                    mContext.getResources().getString(getFallbackCaptionId(mQuickActionCategory));
        } else {
            iconResId = getIconResId(mQuickActionCategory);
            mCaption =
                    mContext.getResources().getString(getFallbackCaptionId(mQuickActionCategory));
        }

        inflate();

        if (iconDrawable != null) {
            ((ImageView) getView()).setImageDrawable(iconDrawable);
        } else {
            ((ImageView) getView()).setImageResource(iconResId);
        }

        invalidate();
    }
}