diff --git a/app/build.gradle b/app/build.gradle index 105da4da..b9be8f48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,6 +48,7 @@ dependencies { compile 'com.jakewharton:butterknife:8.0.1' compile 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1' compile 'info.guardianproject.netcipher:netcipher-webkit:2.0.0-alpha1' + compile "com.android.support:customtabs:24.2.0" apt 'com.jakewharton:butterknife-compiler:8.0.1' } diff --git a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java index 975dff3e..bbe82baf 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java @@ -26,6 +26,8 @@ import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.graphics.drawable.LayerDrawable; +import android.support.v4.view.MenuItemCompat; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -33,7 +35,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.drawable.LayerDrawable; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -42,12 +44,12 @@ import android.os.Handler; import android.os.StrictMode; import android.provider.MediaStore; import android.support.annotation.NonNull; +import android.support.customtabs.CustomTabsIntent; import android.support.design.widget.AppBarLayout; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.GravityCompat; -import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; @@ -79,9 +81,13 @@ import com.github.dfa.diaspora_android.R; import com.github.dfa.diaspora_android.data.AppSettings; import com.github.dfa.diaspora_android.data.PodUserProfile; import com.github.dfa.diaspora_android.listener.WebUserProfileChangedListener; +import com.github.dfa.diaspora_android.receivers.OpenExternalLinkReceiver; +import com.github.dfa.diaspora_android.receivers.UpdateTitleReceiver; import com.github.dfa.diaspora_android.ui.BadgeDrawable; import com.github.dfa.diaspora_android.ui.ContextMenuWebView; import com.github.dfa.diaspora_android.ui.CustomWebViewClient; +import com.github.dfa.diaspora_android.util.CustomTabHelpers.BrowserFallback; +import com.github.dfa.diaspora_android.util.CustomTabHelpers.CustomTabActivityHelper; import com.github.dfa.diaspora_android.util.DiasporaUrlHelper; import com.github.dfa.diaspora_android.util.Helpers; import com.github.dfa.diaspora_android.util.Log; @@ -114,6 +120,7 @@ public class MainActivity extends AppCompatActivity public static final int REQUEST_CODE__ACCESS_EXTERNAL_STORAGE = 124; public static final String ACTION_OPEN_URL = "com.github.dfa.diaspora_android.MainActivity.open_url"; + public static final String ACTION_OPEN_EXTERNAL_URL = "com.github.dfa.diaspora_android.MainActivity.open_external_url"; public static final String ACTION_CHANGE_ACCOUNT = "com.github.dfa.diaspora_android.MainActivity.change_account"; public static final String ACTION_CLEAR_CACHE = "com.github.dfa.diaspora_android.MainActivity.clear_cache"; public static final String ACTION_UPDATE_TITLE_FROM_URL = "com.github.dfa.diaspora_android.MainActivity.set_title"; @@ -126,12 +133,15 @@ public class MainActivity extends AppCompatActivity private ValueCallback imageUploadFilePathCallbackNew; private ValueCallback imageUploadFilePathCallbackOld; private String mCameraPhotoPath; + private CustomTabActivityHelper customTabActivityHelper; private WebSettings webSettings; private AppSettings appSettings; private DiasporaUrlHelper urls; private PodUserProfile podUserProfile; private final Handler uiHandler = new Handler(); private CustomWebViewClient webViewClient; + private OpenExternalLinkReceiver brOpenExternalLink; + private BroadcastReceiver brSetTitle; private Snackbar snackbarExitApp; private Snackbar snackbarNewNotification; private Snackbar snackbarNoInternet; @@ -189,6 +199,7 @@ public class MainActivity extends AppCompatActivity podUserProfile.setCallbackHandler(uiHandler); podUserProfile.setListener(this); urls = new DiasporaUrlHelper(appSettings); + customTabActivityHelper = new CustomTabActivityHelper(); setupUI(savedInstanceState); @@ -200,24 +211,42 @@ public class MainActivity extends AppCompatActivity } else if (appSettings.wasProxyEnabled()) { resetProxy(); } + + brOpenExternalLink = new OpenExternalLinkReceiver(this); + brSetTitle = new UpdateTitleReceiver(app, urls, new UpdateTitleReceiver.TitleCallback() { + @Override + public void setTitle(int rId) { + MainActivity.this.setTitle(rId); + } + + @Override + public void setTitle(String title) { + MainActivity.this.setTitle(title); + } + }); } private void setupUI(Bundle savedInstanceState) { Log.i(App.TAG, "MainActivity.setupUI()"); + ButterKnife.bind(this); + if (webviewPlaceholder.getChildCount() != 0) { + Log.v(App.TAG, "remove child views from webViewPlaceholder"); + webviewPlaceholder.removeAllViews(); + } else { + Log.v(App.TAG, "webViewPlaceholder had no child views"); + } + boolean newWebView = (webView == null); if(newWebView) { - Log.v(App.TAG, "Webview was null. Create new one."); + Log.v(App.TAG, "WebView was null. Create new one."); View webviewHolder = getLayoutInflater().inflate(R.layout.webview, this.contentLayout, false); - webView = (ContextMenuWebView) webviewHolder.findViewById(R.id.webView); + this.webView = (ContextMenuWebView) webviewHolder.findViewById(R.id.webView); ((LinearLayout)webView.getParent()).removeView(webView); setupWebView(savedInstanceState); } else { Log.v(App.TAG, "Reuse old WebView to avoid reloading page"); } - ButterKnife.bind(this); - if (webviewPlaceholder.getChildCount() != 0) { - webviewPlaceholder.removeAllViews(); - } + Log.v(App.TAG, "Add WebView to placeholder"); webviewPlaceholder.addView(webView); // Setup toolbar @@ -689,52 +718,24 @@ public class MainActivity extends AppCompatActivity } } - /** - * BroadcastReceiver that updates the title of the activity based on which url is currently loaded - */ - private final BroadcastReceiver brSetTitle = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String url = intent.getStringExtra(EXTRA_URL); - if (url != null && url.startsWith(urls.getPodUrl())) { - String subUrl = url.substring((urls.getPodUrl()).length()); - Log.v(App.TAG, "MainActivity.brSetTitle.onReceive(): Set title for subUrl "+subUrl); - if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_STREAM)) { - setTitle(R.string.nav_stream); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_POSTS)) { - setTitle(R.string.diaspora); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_NOTIFICATIONS)) { - setTitle(R.string.notifications); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_CONVERSATIONS)) { - setTitle(R.string.conversations); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_NEW_POST)) { - setTitle(R.string.new_post); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_PEOPLE + appSettings.getProfileId())) { - setTitle(R.string.nav_profile); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_ACTIVITY)) { - setTitle(R.string.nav_activities); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_LIKED)) { - setTitle(R.string.nav_liked); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_COMMENTED)) { - setTitle(R.string.nav_commented); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_MENTIONS)) { - setTitle(R.string.nav_mentions); - } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_PUBLIC)) { - setTitle(R.string.public_); - } else if (urls.isAspectUrl(url)){ - setTitle(urls.getAspectNameFromUrl(url, app)); - } - } else { - Log.w(App.TAG, "MainActivity.brSetTitle.onReceive(): Invalid url: "+url); - } - } - }; + @Override + protected void onStart() { + super.onStart(); + customTabActivityHelper.bindCustomTabsService(this); + } + + @Override + protected void onStop() { + super.onStop(); + customTabActivityHelper.unbindCustomTabsService(this); + } @Override protected void onPause() { Log.v(App.TAG, "MainActivity.onPause()"); Log.v(App.TAG, "Unregister BroadcastReceivers"); LocalBroadcastManager.getInstance(this).unregisterReceiver(brSetTitle); + LocalBroadcastManager.getInstance(this).unregisterReceiver(brOpenExternalLink); super.onPause(); } @@ -744,19 +745,20 @@ public class MainActivity extends AppCompatActivity super.onResume(); Log.v(App.TAG, "Register BroadcastReceivers"); LocalBroadcastManager.getInstance(this).registerReceiver(brSetTitle, new IntentFilter(ACTION_UPDATE_TITLE_FROM_URL)); + LocalBroadcastManager.getInstance(this).registerReceiver(brOpenExternalLink, new IntentFilter(ACTION_OPEN_EXTERNAL_URL)); } @Override public boolean onCreateOptionsMenu(Menu menu) { Log.v(App.TAG, "MainActivity.onCreateOptionsMenu()"); getMenuInflater().inflate(R.menu.main__menu_top, menu); - return super.onCreateOptionsMenu(menu); + return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { - Log.i(App.TAG, "MainActivity.onPrepareOptionsMenu()"); MenuItem item; + if ((item = menu.findItem(R.id.action_notifications)) != null) { LayerDrawable icon = (LayerDrawable) item.getIcon(); BadgeDrawable.setBadgeCount(this, icon, podUserProfile.getNotificationCount()); diff --git a/app/src/main/java/com/github/dfa/diaspora_android/receivers/OpenExternalLinkReceiver.java b/app/src/main/java/com/github/dfa/diaspora_android/receivers/OpenExternalLinkReceiver.java new file mode 100644 index 00000000..b9cd46bf --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/receivers/OpenExternalLinkReceiver.java @@ -0,0 +1,50 @@ +package com.github.dfa.diaspora_android.receivers; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.support.customtabs.CustomTabsIntent; + +import com.github.dfa.diaspora_android.App; +import com.github.dfa.diaspora_android.R; +import com.github.dfa.diaspora_android.activity.MainActivity; +import com.github.dfa.diaspora_android.util.CustomTabHelpers.BrowserFallback; +import com.github.dfa.diaspora_android.util.CustomTabHelpers.CustomTabActivityHelper; +import com.github.dfa.diaspora_android.util.Log; + +/** + * BroadcastReceiver that opens links in a Chrome CustomTab + * Created by vanitas on 11.09.16. + */ +public class OpenExternalLinkReceiver extends BroadcastReceiver { + private final Activity parent; + + public OpenExternalLinkReceiver(Activity parent) { + this.parent = parent; + } + + @Override + public void onReceive(Context context, Intent intent) { + String url = intent.getStringExtra(MainActivity.EXTRA_URL); + Log.v(App.TAG, "OpenExternalLinkReceiver.onReceive(): url"); + if(url != null) { + CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(); + if(Build.VERSION.SDK_INT >= 23) { + intentBuilder.setToolbarColor(context.getResources().getColor(R.color.colorPrimary, context.getTheme())); + } else { + intentBuilder.setToolbarColor(context.getResources().getColor(R.color.colorPrimary)); + } + intentBuilder.setStartAnimations(context, android.R.anim.slide_in_left, android.R.anim.fade_out); + intentBuilder.setExitAnimations(context, android.R.anim.fade_in, android.R.anim.slide_out_right); + Bitmap backButtonIcon = BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_arrow_back_white_24px); + intentBuilder.setCloseButtonIcon(backButtonIcon); + CustomTabActivityHelper.openCustomTab(parent, intentBuilder.build(), Uri.parse(url), new BrowserFallback()); + } + } +} diff --git a/app/src/main/java/com/github/dfa/diaspora_android/receivers/UpdateTitleReceiver.java b/app/src/main/java/com/github/dfa/diaspora_android/receivers/UpdateTitleReceiver.java new file mode 100644 index 00000000..9a00599b --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/receivers/UpdateTitleReceiver.java @@ -0,0 +1,79 @@ +package com.github.dfa.diaspora_android.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.github.dfa.diaspora_android.App; +import com.github.dfa.diaspora_android.R; +import com.github.dfa.diaspora_android.activity.MainActivity; +import com.github.dfa.diaspora_android.data.AppSettings; +import com.github.dfa.diaspora_android.util.DiasporaUrlHelper; +import com.github.dfa.diaspora_android.util.Log; + +/** + * BroadcastReceiver used to update the title of the MainActivity depending on the url of the webview + * Created by vanitas on 11.09.16. + */ +public class UpdateTitleReceiver extends BroadcastReceiver { + private DiasporaUrlHelper urls; + private AppSettings appSettings; + private App app; + private TitleCallback callback; + + public UpdateTitleReceiver(App app, DiasporaUrlHelper urls, TitleCallback callback) { + this.urls = urls; + this.app = app; + this.appSettings = app.getSettings(); + this.callback = callback; + } + + @Override + public void onReceive(Context context, Intent intent) { + String url = intent.getStringExtra(MainActivity.EXTRA_URL); + if (url != null && url.startsWith(urls.getPodUrl())) { + String subUrl = url.substring((urls.getPodUrl()).length()); + Log.v(App.TAG, "UpdateTitleReceiver.onReceive(): Set title for subUrl "+subUrl); + if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_STREAM)) { + setTitle(R.string.nav_stream); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_POSTS)) { + setTitle(R.string.diaspora); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_NOTIFICATIONS)) { + setTitle(R.string.notifications); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_CONVERSATIONS)) { + setTitle(R.string.conversations); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_NEW_POST)) { + setTitle(R.string.new_post); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_PEOPLE + appSettings.getProfileId())) { + setTitle(R.string.nav_profile); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_ACTIVITY)) { + setTitle(R.string.nav_activities); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_LIKED)) { + setTitle(R.string.nav_liked); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_COMMENTED)) { + setTitle(R.string.nav_commented); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_MENTIONS)) { + setTitle(R.string.nav_mentions); + } else if (subUrl.startsWith(DiasporaUrlHelper.SUBURL_PUBLIC)) { + setTitle(R.string.public_); + } else if (urls.isAspectUrl(url)){ + setTitle(urls.getAspectNameFromUrl(url, app)); + } + } else { + Log.w(App.TAG, "UpdateTitleReceiver.onReceive(): Invalid url: "+url); + } + } + + private void setTitle(int rId) { + callback.setTitle(rId); + } + + private void setTitle(String title) { + callback.setTitle(title); + } + + public interface TitleCallback { + void setTitle(int Rid); + void setTitle(String title); + } +} diff --git a/app/src/main/java/com/github/dfa/diaspora_android/ui/CustomWebViewClient.java b/app/src/main/java/com/github/dfa/diaspora_android/ui/CustomWebViewClient.java index 36150888..9a7832ac 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/ui/CustomWebViewClient.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/ui/CustomWebViewClient.java @@ -20,11 +20,13 @@ package com.github.dfa.diaspora_android.ui; import android.content.Intent; import android.net.Uri; +import android.support.v4.content.LocalBroadcastManager; import android.webkit.CookieManager; import android.webkit.WebView; import android.webkit.WebViewClient; import com.github.dfa.diaspora_android.App; +import com.github.dfa.diaspora_android.activity.MainActivity; public class CustomWebViewClient extends WebViewClient { private final App app; @@ -37,9 +39,9 @@ public class CustomWebViewClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { if (!url.contains(app.getSettings().getPodDomain())) { - Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - app.getApplicationContext().startActivity(i); + Intent i = new Intent(MainActivity.ACTION_OPEN_EXTERNAL_URL); + i.putExtra(MainActivity.EXTRA_URL, url); + LocalBroadcastManager.getInstance(app.getApplicationContext()).sendBroadcast(i); return true; } return false; diff --git a/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/BrowserFallback.java b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/BrowserFallback.java new file mode 100644 index 00000000..a4bdd930 --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/BrowserFallback.java @@ -0,0 +1,18 @@ +package com.github.dfa.diaspora_android.util.CustomTabHelpers; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +/** + * Adapted from https://medium.com/ribot-labs/exploring-chrome-customs-tabs-on-android-ef427effe2f4 + */ + +public class BrowserFallback implements CustomTabActivityHelper.CustomTabFallback { + @Override + public void openUri(Activity activity, Uri uri) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(uri); + activity.startActivity(intent); + } +} diff --git a/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabActivityHelper.java b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabActivityHelper.java new file mode 100644 index 00000000..9e14c748 --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabActivityHelper.java @@ -0,0 +1,149 @@ +package com.github.dfa.diaspora_android.util.CustomTabHelpers; + +import android.app.Activity; +import android.content.ComponentName; +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 android.util.Log; + +import java.util.List; + +/** + * Adapted from https://medium.com/ribot-labs/exploring-chrome-customs-tabs-on-android-ef427effe2f4 + */ + +public class CustomTabActivityHelper { + 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, + CustomTabFallback fallback) { + String packageName = CustomTabsHelper.getPackageNameToUse(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 webview + if (packageName == null) { + if (fallback != null) { + fallback.openUri(activity, uri); + } + } else { + customTabsIntent.intent.setPackage(packageName); + customTabsIntent.launchUrl(activity, uri); + } + } + + /** + * 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; + } + + /** + * 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 CustomTabsServiceConnection() { + @Override + public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { + mClient = client; + mClient.warmup(0L); + if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected(); + //Initialize a session as soon as possible. + getSession(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mClient = null; + if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected(); + } + }; + CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection); + } + + public boolean mayLaunchUrl(Uri uri, Bundle extras, List otherLikelyBundles) { + if (mClient == null) return false; + + CustomTabsSession session = getSession(); + if (session == null) return false; + + return session.mayLaunchUrl(uri, extras, otherLikelyBundles); + } + + /** + * 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 diff --git a/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabsHelper.java b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabsHelper.java new file mode 100644 index 00000000..417c62a7 --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/util/CustomTabHelpers/CustomTabsHelper.java @@ -0,0 +1,121 @@ +package com.github.dfa.diaspora_android.util.CustomTabHelpers; + +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.CustomTabsService; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for Custom Tabs. Adapted from https://medium.com/ribot-labs/exploring-chrome-customs-tabs-on-android-ef427effe2f4 + */ +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 String sPackageNameToUse; + + private CustomTabsHelper() {} + + /** + * 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 not 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 resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List 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()) { + 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 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}; + } +} diff --git a/app/src/main/res/layout/webview.xml b/app/src/main/res/layout/webview.xml index 7bece82a..35d12bc4 100644 --- a/app/src/main/res/layout/webview.xml +++ b/app/src/main/res/layout/webview.xml @@ -1,12 +1,13 @@ - + android:orientation="vertical" android:layout_width="fill_parent" + android:layout_height="fill_parent"> + android:layout_height="fill_parent" + android:layout_alignParentEnd="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" /> \ No newline at end of file