13

I have an activity that loads an external url into a webview within my app. I'd like to use Chrome Custom tabs when it's available but I support devices that might not have a version of Chrome that supports them.

In the case of CustomTabs not being supported I'd like to use my old code but use the CustomTabsIntent.Builder() when they are. The old code loads the content in a WebView contained in an Activity where I can still manage the ActionBar.

I'd like to write a helper method that will tell me if it's supported but I'm not sure how. The info on the developer page is pretty slim: https://developer.chrome.com/multidevice/android/customtabs

It says if you bind succeeds the custom tabs can be safely used. Is there an easy way to bind to test this?

Like this I assume:

Intent serviceIntent = new Intent("android.support.customtabs.action.CustomTabsService");
serviceIntent.setPackage("com.android.chrome");
boolean customTabsSupported = bindService(serviceIntent, new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(final ComponentName componentName, final CustomTabsClient customTabsClient) {}

            @Override
            public void onServiceDisconnected(final ComponentName name) {}
        },
        Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);

if (customTabsSupported) {
    // is supported
}
Jeff
  • 2,198
  • 3
  • 21
  • 38

6 Answers6

14

Instead of binding and unbinding the service, you can use the PackageManager to check if Custom Tabs is supported.

  private static final String SERVICE_ACTION = "android.support.customtabs.action.CustomTabsService";
    private static final String CHROME_PACKAGE = "com.android.chrome";

    private static boolean isChromeCustomTabsSupported(@NonNull final Context context) {
        Intent serviceIntent = new Intent(SERVICE_ACTION);
        serviceIntent.setPackage(CHROME_PACKAGE);
        List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(serviceIntent, 0);
        return !(resolveInfos == null || resolveInfos.isEmpty());
    }

Be aware that other browsers may support Custom Tabs in the future, so you may want to modify that to support this case.

andreban
  • 4,621
  • 1
  • 20
  • 49
  • I've quickly tested without setting the package and it seems to be working too if you just want to check if any app supports custom tabs. – natronite Apr 05 '19 at 07:45
8

You can try following code to figure out if you have a browser that supports custom tab:

private static final String TAG = "CustomTabLauncher";
static final String STABLE_PACKAGE = "com.android.chrome";
static final String BETA_PACKAGE = "com.chrome.beta";
static final String DEV_PACKAGE = "com.chrome.dev";
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
String mPackageNameToUse;

private String getPackageName(Context context) {
    if (mPackageNameToUse != null) {
        return mPackageNameToUse;
    }

    // Get default VIEW intent handler that can view a web url.
    Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.test-url.com"));

    // Get all apps that can handle VIEW intents.
    PackageManager pm = context.getPackageManager();
    List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
    List<String> packagesSupportingCustomTabs = new ArrayList<>();
    for (ResolveInfo info : resolvedActivityList) {
        Intent serviceIntent = new Intent();
        serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
        serviceIntent.setPackage(info.activityInfo.packageName);
        if (pm.resolveService(serviceIntent, 0) != null) {
            packagesSupportingCustomTabs.add(info.activityInfo.packageName);
        }
    }

    // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
    // and service calls.
    if (packagesSupportingCustomTabs.isEmpty()) {
        mPackageNameToUse = null;
    } else if (packagesSupportingCustomTabs.size() == 1) {
        mPackageNameToUse = packagesSupportingCustomTabs.get(0);
    } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
        mPackageNameToUse = STABLE_PACKAGE;
    } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
        mPackageNameToUse = BETA_PACKAGE;
    } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
        mPackageNameToUse = DEV_PACKAGE;
    } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
        mPackageNameToUse = LOCAL_PACKAGE;
    }
    return mPackageNameToUse;
}

When calling, you can do something like this:

public void openCustomTab(Uri uri, Activity activity) {
    //If we cant find a package name, it means there's no browser that supports
    //Chrome Custom Tabs installed. So, we fallback to the default browser
    if (getPackageName(activity) == null) {
        activity.startActivity(new Intent(Intent.ACTION_VIEW, uri));
    } else {
        CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();
        intentBuilder.enableUrlBarHiding();
        intentBuilder.setToolbarColor(activity.getResources().getColor(R.color.purple_a_01));

        CustomTabsIntent customTabsIntent = intentBuilder.build();
        customTabsIntent.intent.setPackage(mPackageNameToUse);

        customTabsIntent.launchUrl(activity, uri);
    }
}
Androidme
  • 3,145
  • 3
  • 24
  • 27
  • 1
    Fantastic answer, can be modified to return a simple boolean indicating the existence of a Chrome Tab compatible browser. https://gist.github.com/aashreys/fd8a14e652b7c80b784dc90be235d208 – aashreys Feb 05 '17 at 17:45
  • really a good solution, but `getPackageName()`shoud call eveytime before you open a url. the `mPackageNameToUse` last time you get , the represent browser may not exist if user uninstall it. so if still use last get browser package name may cause `no activity can handle intent` exception. – Loyea Feb 07 '19 at 15:23
6

I ended up writing a static method in my Utils class so I can check and handle the case where it isn't supported:

/**
     * Check if Chrome CustomTabs are supported. 
     * Some devices don't have Chrome or it may not be
     * updated to a version where custom tabs is supported.
     *
     * @param context the context
     * @return whether custom tabs are supported
     */
    public static boolean isChromeCustomTabsSupported(@NonNull final Context context) {
        Intent serviceIntent = new Intent("android.support.customtabs.action.CustomTabsService");
        serviceIntent.setPackage("com.android.chrome");

        CustomTabsServiceConnection serviceConnection = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(final ComponentName componentName, final CustomTabsClient customTabsClient) { }

            @Override
            public void onServiceDisconnected(final ComponentName name) { }
        };

        boolean customTabsSupported =
                context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
        context.unbindService(serviceConnection);

        return customTabsSupported;
    }
Jeff
  • 2,198
  • 3
  • 21
  • 38
4

New issues may arise if you are targeting API level 30 and running on an Android 11 device.

You will need to add a section to your manifest, such as:

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
</queries>

OR

<queries>
    <intent>
        <action android:name="android.support.customtabs.action.CustomTabsService" />
    </intent>
</queries>

Without this, some of the calls to PackageManager mentioned in the above posts will not work as you expect.

https://developer.android.com/training/package-visibility/use-cases

ODB
  • 366
  • 1
  • 5
3

I solved this problem by handling ActivityNotFound exception in catch block.

The trick is to check if the browser activity of chrome can be started or not, if it can't be started or throws an exception then simply open the link through Intent.ACTION_VIEW.

Here is all the relevant code ....

private void onBtnLinkClicked(View v, int pos) {
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            openCustomTab(url);
        } else {
            openBrowserActivity(url);
        }
    } catch (Exception e) {
        e.printStackTrace();
        openBrowserActivity(url);
    }
}

private void openBrowserActivity(String url) {
    Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    context.startActivity(browserIntent);
}

What is openCustomTab(url) you say : Here is the relevant code for it.

private void openCustomTab(String url) {
    CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();

    int color = context.getResources().getColor(R.color.colorPrimary);
    intentBuilder.setToolbarColor(color);
    intentBuilder.setShowTitle(true);

    String menuItemTitle = context.getString(R.string.menu_title_share);
    PendingIntent menuItemPendingIntent = createPendingShareIntent();
    intentBuilder.addMenuItem(menuItemTitle, menuItemPendingIntent);     

    intentBuilder.setStartAnimations(context,
            R.anim.slide_in_right, R.anim.slide_out_left);
    intentBuilder.setExitAnimations(context,
            android.R.anim.slide_in_left, android.R.anim.slide_out_right);

    CustomTabActivityHelper.openCustomTab(
            activity, intentBuilder.build(), Uri.parse(url), new WebviewFallback());
}

My style of answer may seem cocky but before clicking downvote let me know if you have run into any unexpected bug or any other problem that this approach may have cause. Do give your feedback, we are a community afterall.

Anudeep Samaiya
  • 1,910
  • 2
  • 28
  • 33
1

From developer site of Chrome, I found the followings -

As of Chrome 45, Chrome Custom Tabs is now generally available to all users of Chrome, on all of Chrome's supported Android versions (Jellybean onwards).

Link: https://developer.chrome.com/multidevice/android/customtabs#whencaniuseit

So, I checked whether Chrome supports Chrome Custom Tab by version.

Check my code:

String chromePackageName = "com.android.chrome";
int chromeTargetVersion  = 45;

boolean isSupportCustomTab = false;
try {
    String chromeVersion = getApplicationContext().getPackageManager().getPackageInfo(chromePackageName, 0).versionName;
    if(chromeVersion.contains(".")) {
        chromeVersion = chromeVersion.substring(0, chromeVersion.indexOf('.'));
    }
    isSupportCustomTab = (Integer.valueOf(chromeVersion) >= chromeTargetVersion);
} catch (PackageManager.NameNotFoundException ex) {
} catch (Exception ex) { }

if (isSupportCustomTab) {
    //Use Chrome Custom Tab
} else {
    //Use WebView or other Browser
}

I don't know how efficient it is, just want to share.

Community
  • 1
  • 1
mdrafiqulrabin
  • 1,657
  • 5
  • 28
  • 38