Commit c4afea91 authored by pichillilorenzo's avatar pichillilorenzo

added initial support for Chrome Custom Tabs in android

parent 141da409
This diff is collapsed.
......@@ -4,9 +4,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:theme="@style/AppTheme" >
<activity android:name=".WebViewActivity" android:configChanges="orientation|screenSize"></activity>
<application>
<activity android:theme="@style/AppTheme" android:name=".WebViewActivity" android:configChanges="orientation|screenSize"></activity>
<activity android:theme="@style/ThemeTransparent" android:name=".chrome_custom_tabs.ChromeCustomTabsActivity" android:configChanges="orientation|screenSize"></activity>
</application>
</manifest>
\ No newline at end of file
......@@ -21,9 +21,7 @@
package com.pichillilorenzo.flutter_inappbrowser;
import android.annotation.TargetApi;
import android.app.Activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
......@@ -32,7 +30,6 @@ import android.provider.Browser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.util.JsonReader;
import android.util.JsonToken;
import android.webkit.MimeTypeMap;
......@@ -41,6 +38,8 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.util.Log;
import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.ChromeCustomTabsActivity;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
......@@ -61,6 +60,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
public Activity activity;
public static MethodChannel channel;
public static WebViewActivity webViewActivity;
public static ChromeCustomTabsActivity chromeCustomTabsActivity;
private static final String NULL = "null";
protected static final String LOG_TAG = "InAppBrowserFlutterP";
......@@ -102,6 +102,11 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
this.activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (options.useChromeCustomTabs) {
open(url, options);
}
else {
if ("_self".equals(target)) {
Log.d(LOG_TAG, "in self");
......@@ -133,7 +138,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
Log.d(LOG_TAG, "in blank");
open(url, options);
}
}
result.success(true);
}
});
......@@ -360,8 +365,8 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
}
}
private void open(final String url, InAppBrowserOptions options) {
Intent intent = new Intent(activity, WebViewActivity.class);
public static void open(final String url, InAppBrowserOptions options) {
Intent intent = new Intent(registrar.activity(), (options.useChromeCustomTabs) ? ChromeCustomTabsActivity.class : WebViewActivity.class);
Bundle extras = new Bundle();
extras.putString("url", url);
......@@ -369,7 +374,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
intent.putExtras(extras);
activity.startActivity(intent);
registrar.activity().startActivity(intent);
}
public void loadUrl(String url, Map<String, String> headers, Result result) {
......
......@@ -9,28 +9,35 @@ import java.util.HashMap;
public class InAppBrowserOptions {
boolean useShouldOverrideUrlLoading = false;
boolean clearCache = false;
String userAgent = "";
boolean javaScriptEnabled = true;
boolean javaScriptCanOpenWindowsAutomatically = false;
boolean hidden = false;
boolean toolbarTop = true;
String toolbarTopBackgroundColor = "";
String toolbarTopFixedTitle = "";
boolean hideUrlBar = false;
boolean mediaPlaybackRequiresUserGesture = true;
boolean hideTitleBar = false;
boolean closeOnCannotGoBack = true;
boolean clearSessionCache = false;
boolean builtInZoomControls = false;
boolean supportZoom = true;
boolean databaseEnabled = false;
boolean domStorageEnabled = false;
boolean useWideViewPort = true;
boolean safeBrowsingEnabled = true;
boolean progressBar = true;
public boolean useShouldOverrideUrlLoading = false;
public boolean clearCache = false;
public String userAgent = "";
public boolean javaScriptEnabled = true;
public boolean javaScriptCanOpenWindowsAutomatically = false;
public boolean hidden = false;
public boolean toolbarTop = true;
public String toolbarTopBackgroundColor = "";
public String toolbarTopFixedTitle = "";
public boolean hideUrlBar = false;
public boolean mediaPlaybackRequiresUserGesture = true;
public boolean hideTitleBar = false;
public boolean closeOnCannotGoBack = true;
public boolean clearSessionCache = false;
public boolean builtInZoomControls = false;
public boolean supportZoom = true;
public boolean databaseEnabled = false;
public boolean domStorageEnabled = false;
public boolean useWideViewPort = true;
public boolean safeBrowsingEnabled = true;
public boolean progressBar = true;
public boolean useChromeCustomTabs = false;
public boolean CCT_addShareButton = true;
public boolean CCT_showTitle = true;
public String CCT_toolbarColor = "";
public boolean CCT_enableUrlBarHiding = false;
public boolean CCT_instantAppsEnabled = false;
public void parse(HashMap<String, Object> options) {
Iterator it = options.entrySet().iterator();
......
......@@ -61,7 +61,7 @@ public class WebViewActivity extends AppCompatActivity {
}
public void prepareWebView() {
private void prepareWebView() {
inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this);
webView.setWebChromeClient(inAppBrowserWebChromeClient);
......
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsIntent;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserOptions;
import com.pichillilorenzo.flutter_inappbrowser.R;
import java.util.HashMap;
public class ChromeCustomTabsActivity extends Activity {
CustomTabsIntent.Builder builder;
InAppBrowserOptions options;
private CustomTabActivityHelper customTabActivityHelper;
private final int CHROME_CUSTOM_TAB_REQUEST_CODE = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chrome_custom_tabs_layout);
Bundle b = getIntent().getExtras();
String url = b.getString("url");
options = new InAppBrowserOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options"));
customTabActivityHelper = new CustomTabActivityHelper();
builder = new CustomTabsIntent.Builder();
prepareCustomTabs();
CustomTabsIntent customTabsIntent = builder.build();
customTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), CHROME_CUSTOM_TAB_REQUEST_CODE,
new CustomTabActivityHelper.CustomTabFallback() {
@Override
public void openUri(Activity activity, Uri uri) {
InAppBrowserFlutterPlugin.open(uri.toString(), options);
}
});
}
private void prepareCustomTabs() {
if (options.CCT_addShareButton)
builder.addDefaultShareMenuItem();
if (!options.CCT_toolbarColor.isEmpty())
builder.setToolbarColor(Color.parseColor(options.CCT_toolbarColor));
builder.setShowTitle(options.CCT_showTitle);
if (options.CCT_enableUrlBarHiding)
builder.enableUrlBarHiding();
builder.setInstantAppsEnabled(options.CCT_instantAppsEnabled);
}
@Override
protected void onStart() {
super.onStart();
customTabActivityHelper.bindCustomTabsService(this);
}
@Override
protected void onStop() {
super.onStop();
customTabActivityHelper.unbindCustomTabsService(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
finish();
}
}
}
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsServiceConnection;
import android.support.customtabs.CustomTabsSession;
import java.util.List;
import static android.support.v4.app.ActivityCompat.startActivityForResult;
/**
* This is a helper class to manage the connection to the Custom Tabs Service.
*/
public class CustomTabActivityHelper implements ServiceConnectionCallback {
private CustomTabsSession mCustomTabsSession;
private CustomTabsClient mClient;
private CustomTabsServiceConnection mConnection;
private ConnectionCallback mConnectionCallback;
/**
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
*
* @param activity The host activity.
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
* @param uri the Uri to be opened.
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
*/
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
int requestCode,
CustomTabFallback fallback) {
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
//If we cant find a package name, it means theres no browser that supports
//Chrome Custom Tabs installed. So, we fallback to the webview
if (packageName == null) {
if (fallback != null) {
fallback.openUri(activity, uri);
}
} else {
customTabsIntent.intent.setPackage(packageName);
customTabsIntent.intent.setData(uri);
activity.startActivityForResult(customTabsIntent.intent, requestCode);
}
}
/**
* Unbinds the Activity from the Custom Tabs Service.
* @param activity the activity that is connected to the service.
*/
public void unbindCustomTabsService(Activity activity) {
if (mConnection == null) return;
activity.unbindService(mConnection);
mClient = null;
mCustomTabsSession = null;
mConnection = null;
}
/**
* Creates or retrieves an exiting CustomTabsSession.
*
* @return a CustomTabsSession.
*/
public CustomTabsSession getSession() {
if (mClient == null) {
mCustomTabsSession = null;
} else if (mCustomTabsSession == null) {
mCustomTabsSession = mClient.newSession(null);
}
return mCustomTabsSession;
}
/**
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
* @param connectionCallback
*/
public void setConnectionCallback(ConnectionCallback connectionCallback) {
this.mConnectionCallback = connectionCallback;
}
/**
* Binds the Activity to the Custom Tabs Service.
* @param activity the activity to be binded to the service.
*/
public void bindCustomTabsService(Activity activity) {
if (mClient != null) return;
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
if (packageName == null) return;
mConnection = new ServiceConnection(this);
CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
}
/**
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
* @return true if call to mayLaunchUrl was accepted.
*/
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
if (mClient == null) return false;
CustomTabsSession session = getSession();
if (session == null) return false;
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
}
@Override
public void onServiceConnected(CustomTabsClient client) {
mClient = client;
mClient.warmup(0L);
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
}
@Override
public void onServiceDisconnected() {
mClient = null;
mCustomTabsSession = null;
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
}
/**
* A Callback for when the service is connected or disconnected. Use those callbacks to
* handle UI changes when the service is connected or disconnected.
*/
public interface ConnectionCallback {
/**
* Called when the service is connected.
*/
void onCustomTabsConnected();
/**
* Called when the service is disconnected.
*/
void onCustomTabsDisconnected();
}
/**
* To be used as a fallback to open the Uri when Custom Tabs is not available.
*/
public interface CustomTabFallback {
/**
*
* @param activity The Activity that wants to open the Uri.
* @param uri The uri to be opened by the fallback.
*/
void openUri(Activity activity, Uri uri);
}
}
\ No newline at end of file
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.support.customtabs.CustomTabsClient;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for Custom Tabs.
*/
public class CustomTabsHelper {
private static final String TAG = "CustomTabsHelper";
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";
private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
"android.support.customtabs.extra.KEEP_ALIVE";
private static final String ACTION_CUSTOM_TABS_CONNECTION =
"android.support.customtabs.action.CustomTabsService";
private static String sPackageNameToUse;
private CustomTabsHelper() {}
public static void addKeepAliveExtra(Context context, Intent intent) {
Intent keepAliveIntent = new Intent().setClassName(
context.getPackageName(), KeepAliveService.class.getCanonicalName());
intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent);
}
/**
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
* the one chosen by the user if there is one, otherwise makes a best effort to return a
* valid package name.
*
* This is <strong>not</strong> threadsafe.
*
* @param context {@link Context} to use for accessing {@link PackageManager}.
* @return The package name recommended to use for connecting to custom tabs related components.
*/
public static String getPackageNameToUse(Context context) {
if (sPackageNameToUse != null) return sPackageNameToUse;
PackageManager pm = context.getPackageManager();
// Get default VIEW intent handler.
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
String defaultViewHandlerPackageName = null;
if (defaultViewHandlerInfo != null) {
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
}
// Get all apps that can handle VIEW intents.
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> packagesSupportingCustomTabs = new ArrayList<>();
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(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()) {
sPackageNameToUse = null;
} else if (packagesSupportingCustomTabs.size() == 1) {
sPackageNameToUse = packagesSupportingCustomTabs.get(0);
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
&& !hasSpecializedHandlerIntents(context, activityIntent)
&& packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
sPackageNameToUse = defaultViewHandlerPackageName;
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
sPackageNameToUse = STABLE_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
sPackageNameToUse = BETA_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
sPackageNameToUse = DEV_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
sPackageNameToUse = LOCAL_PACKAGE;
}
return sPackageNameToUse;
}
/**
* Used to check whether there is a specialized handler for a given intent.
* @param intent The intent to check with.
* @return Whether there is a specialized handler for the given intent.
*/
private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
try {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> handlers = pm.queryIntentActivities(
intent,
PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.size() == 0) {
return false;
}
for (ResolveInfo resolveInfo : handlers) {
IntentFilter filter = resolveInfo.filter;
if (filter == null) continue;
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
if (resolveInfo.activityInfo == null) continue;
return true;
}
} catch (RuntimeException e) {
Log.e(TAG, "Runtime exception while getting specialized handlers");
}
return false;
}
/**
* @return All possible chrome package names that provide custom tabs feature.
*/
public static String[] getPackages() {
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
}
}
\ No newline at end of file
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
/**
* Empty service used by the custom tab to bind to, raising the application's importance.
*/
public class KeepAliveService extends Service {
private static final Binder sBinder = new Binder();
@Override
public IBinder onBind(Intent intent) {
return sBinder;
}
}
\ No newline at end of file
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.content.ComponentName;
import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsServiceConnection;
import java.lang.ref.WeakReference;
/**
* Implementation for the CustomTabsServiceConnection that avoids leaking the
* ServiceConnectionCallback
*/
public class ServiceConnection extends CustomTabsServiceConnection {
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
mConnectionCallback = new WeakReference<>(connectionCallback);
}
@Override
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
}
@Override
public void onServiceDisconnected(ComponentName name) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
}
}
package com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs;
import android.support.customtabs.CustomTabsClient;
/**
* Callback for events when connecting and disconnecting from Custom Tabs Service.
*/
public interface ServiceConnectionCallback {
/**
* Called when the service is connected.
* @param client a CustomTabsClient
*/
void onServiceConnected(CustomTabsClient client);
/**
* Called when the service is disconnected.
*/
void onServiceDisconnected();
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
......@@ -3,4 +3,13 @@
<style name="AppTheme" parent="Theme.AppCompat.Light">
</style>
<style name="ThemeTransparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
......@@ -87,9 +87,10 @@ class _MyAppState extends State<MyApp> {
body: new Center(
child: new RaisedButton(onPressed: () {
inAppBrowser.open("https://flutter.io/", options: {
"useChromeCustomTabs": true,
//"hidden": true,
//"toolbarTopFixedTitle": "Fixed title",
"useShouldOverrideUrlLoading": true
//"useShouldOverrideUrlLoading": true
//"hideUrlBar": true,
//"toolbarTop": false,
//"toolbarBottom": false
......
name: flutter_inappbrowser
description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser).
version: 0.1.0
version: 0.1.1
author: Lorenzo Pichilli <pichillilorenzo@gmail.com>
homepage: https://github.com/pichillilorenzo/flutter_inappbrowser
......
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