diff --git a/.gitignore b/.gitignore
index b8cd4a42..c7b11955 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@ build/
.idea
release.properties
crashlytics.properties
+*.iml
diff --git a/app/build.gradle b/app/build.gradle
index 21ff49ba..988503fb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,10 +12,10 @@ apply plugin: 'android-apt'
dependencies {
apt "org.androidannotations:androidannotations:3.2"
- compile "org.androidannotations:androidannotations-api:3.2"
- compile 'com.android.support:appcompat-v7:18.0.+'
- compile 'com.android.support:support-v4:21.0.+'
-
+ compile 'org.androidannotations:androidannotations-api:3.2'
+ compile 'com.android.support:appcompat-v7:23.3.0'
+ compile 'com.android.support:support-v4:23.3.0'
+ compile 'com.android.support:customtabs:23.3.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
}
@@ -23,13 +23,14 @@ android {
compileOptions {
encoding "UTF-8"
}
- compileSdkVersion 19
- buildToolsVersion '19.1.0'
+ compileSdkVersion 23
+ buildToolsVersion '23.0.2'
+ useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "com.manuelmaly.hn"
- minSdkVersion 8
- targetSdkVersion 19
+ minSdkVersion 15
+ targetSdkVersion 23
versionCode 25
versionName "1.9.15"
}
diff --git a/app/src/main/java/com/manuelmaly/hn/ArticleReaderActivity.java b/app/src/main/java/com/manuelmaly/hn/ArticleReaderActivity.java
index 70647c28..8378d123 100644
--- a/app/src/main/java/com/manuelmaly/hn/ArticleReaderActivity.java
+++ b/app/src/main/java/com/manuelmaly/hn/ArticleReaderActivity.java
@@ -1,6 +1,7 @@
package com.manuelmaly.hn;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -24,6 +25,7 @@
import com.manuelmaly.hn.util.FontHelper;
import com.manuelmaly.hn.util.SpotlightActivity;
import com.manuelmaly.hn.util.ViewedUtils;
+import com.manuelmaly.hn.util.CustomTabActivityHelper;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
@@ -33,7 +35,7 @@
import java.net.URLEncoder;
@EActivity(R.layout.article_activity)
-public class ArticleReaderActivity extends ActionBarActivity {
+public class ArticleReaderActivity extends ActionBarActivity implements CustomTabActivityHelper.CustomTabFallback {
public static final int ACTIVITY_LOGIN = 137;
@@ -265,6 +267,17 @@ private void setShowRefreshing(boolean showRefreshing) {
}
}
+ @Override
+ public void openUri(Activity activity, HNPost post, String overrideHtmlProvider) {
+ Intent i = new Intent(activity, ArticleReaderActivity_.class);
+ i.putExtra(ArticleReaderActivity.EXTRA_HNPOST, post);
+ if (overrideHtmlProvider != null) {
+ i.putExtra(ArticleReaderActivity.EXTRA_HTMLPROVIDER_OVERRIDE,
+ overrideHtmlProvider);
+ }
+ activity.startActivity(i);
+ }
+
private class HNReaderWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
diff --git a/app/src/main/java/com/manuelmaly/hn/CommentsActivity.java b/app/src/main/java/com/manuelmaly/hn/CommentsActivity.java
index e9a1c7f7..69960ca6 100644
--- a/app/src/main/java/com/manuelmaly/hn/CommentsActivity.java
+++ b/app/src/main/java/com/manuelmaly/hn/CommentsActivity.java
@@ -11,6 +11,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.support.customtabs.CustomTabsIntent;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.Html;
@@ -48,6 +49,7 @@
import com.manuelmaly.hn.util.FontHelper;
import com.manuelmaly.hn.util.SpotlightActivity;
import com.manuelmaly.hn.util.ViewedUtils;
+import com.manuelmaly.hn.util.CustomTabActivityHelper;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
@@ -144,6 +146,9 @@ public void onClick(View v) {
CommentsActivity.this);
MainActivity.openURLInBrowser(articleURL,
CommentsActivity.this);
+ } else if (Settings.getHtmlViewer(CommentsActivity.this).equals(
+ getString(R.string.pref_htmlviewer_chromecustomtabs))) {
+ MainActivity.openURLInChromeCustomTabs(mPost, null, CommentsActivity.this);
} else {
openArticleReader();
}
@@ -368,20 +373,11 @@ private void showCommentsSpotlight() {
}
private void openArticleReader() {
- Intent intent = new Intent(this, ArticleReaderActivity_.class);
- intent.putExtra(CommentsActivity.EXTRA_HNPOST, mPost);
- if (getIntent().getStringExtra(
- ArticleReaderActivity.EXTRA_HTMLPROVIDER_OVERRIDE) != null) {
- intent.putExtra(
- ArticleReaderActivity.EXTRA_HTMLPROVIDER_OVERRIDE,
- getIntent().getStringExtra(
- ArticleReaderActivity.EXTRA_HTMLPROVIDER_OVERRIDE));
- }
-
- startActivity(intent);
- overridePendingTransition(android.R.anim.fade_in,
- android.R.anim.fade_out);
- finish();
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ CustomTabsIntent customTabsIntent = builder.build();
+ CustomTabActivityHelper.openCustomTab(
+ this, customTabsIntent, mPost, Settings.getHtmlProvider(CommentsActivity.this),
+ new ArticleReaderActivity());
}
private void initCommentsHeader() {
diff --git a/app/src/main/java/com/manuelmaly/hn/MainActivity.java b/app/src/main/java/com/manuelmaly/hn/MainActivity.java
index 84849b2c..dc18736e 100644
--- a/app/src/main/java/com/manuelmaly/hn/MainActivity.java
+++ b/app/src/main/java/com/manuelmaly/hn/MainActivity.java
@@ -10,6 +10,7 @@
import com.manuelmaly.hn.task.ITaskFinishedHandler;
import com.manuelmaly.hn.util.FileUtil;
import com.manuelmaly.hn.util.FontHelper;
+import com.manuelmaly.hn.util.CustomTabActivityHelper;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background;
@@ -19,6 +20,7 @@
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -26,9 +28,12 @@
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.customtabs.CustomTabsIntent;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
@@ -503,6 +508,9 @@ else if (Settings.getHtmlViewer(MainActivity.this).equals(
openURLInBrowser(
getArticleViewURL(getItem(position)),
MainActivity.this);
+ } else if (Settings.getHtmlViewer(MainActivity.this).equals(
+ getString(R.string.pref_htmlviewer_chromecustomtabs))) {
+ openURLInChromeCustomTabs(getItem(position), null, MainActivity.this);
} else {
openPostInApp(getItem(position), null,
MainActivity.this);
@@ -721,6 +729,22 @@ public static void openURLInBrowser(String url, Activity a) {
a.startActivity(browserIntent);
}
+ public static void openURLInChromeCustomTabs(HNPost post, String overrideHtmlProvider, Activity a) {
+ Intent commentsIntent = new Intent(a, CommentsActivity_.class);
+ commentsIntent.putExtra(CommentsActivity.EXTRA_HNPOST, post);
+ PendingIntent pendingIntent = PendingIntent.getActivity(a, 1, commentsIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ Bitmap icon = BitmapFactory.decodeResource(a.getResources(),
+ R.drawable.ic_launcher);
+
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ builder.setActionButton(icon, a.getString(R.string.comments), pendingIntent);
+ CustomTabsIntent customTabsIntent = builder.build();
+ CustomTabActivityHelper.openCustomTab(
+ a, customTabsIntent, post, overrideHtmlProvider, new ArticleReaderActivity());
+ }
+
public static void openPostInApp(HNPost post, String overrideHtmlProvider,
Activity a) {
Intent i = new Intent(a, ArticleReaderActivity_.class);
diff --git a/app/src/main/java/com/manuelmaly/hn/util/CustomTabActivityHelper.java b/app/src/main/java/com/manuelmaly/hn/util/CustomTabActivityHelper.java
new file mode 100644
index 00000000..05b65885
--- /dev/null
+++ b/app/src/main/java/com/manuelmaly/hn/util/CustomTabActivityHelper.java
@@ -0,0 +1,70 @@
+package com.manuelmaly.hn.util;
+
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import android.app.Activity;
+import android.net.Uri;
+import android.support.customtabs.CustomTabsIntent;
+
+import com.manuelmaly.hn.model.HNPost;
+
+import java.util.List;
+
+/**
+ * This is a helper class to manage the connection to the Custom Tabs Service.
+ */
+public class CustomTabActivityHelper {
+
+ /**
+ * 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 post The HNPost article that must be opened
+ * @param overrideHtmlProvider something something HTML
+ * @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
+ */
+ public static void openCustomTab(Activity activity,
+ CustomTabsIntent customTabsIntent,
+ HNPost post,
+ String overrideHtmlProvider,
+ 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, post, overrideHtmlProvider);
+ }
+ } else {
+ customTabsIntent.intent.setPackage(packageName);
+ customTabsIntent.launchUrl(activity, Uri.parse(post.getURL()));
+ }
+ }
+
+ /**
+ * 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 post The HNPost article that must be opened
+ * @param overrideHtmlProvider something something HTML
+ */
+ void openUri(Activity activity, HNPost post, String overrideHtmlProvider);
+ }
+}
diff --git a/app/src/main/java/com/manuelmaly/hn/util/CustomTabsHelper.java b/app/src/main/java/com/manuelmaly/hn/util/CustomTabsHelper.java
new file mode 100644
index 00000000..ac867c6a
--- /dev/null
+++ b/app/src/main/java/com/manuelmaly/hn/util/CustomTabsHelper.java
@@ -0,0 +1,136 @@
+package com.manuelmaly.hn.util;
+
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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.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() {}
+
+ /**
+ * 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(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/values/preference_values.xml b/app/src/main/res/values/preference_values.xml
index dd65ea52..d225c622 100644
--- a/app/src/main/res/values/preference_values.xml
+++ b/app/src/main/res/values/preference_values.xml
@@ -29,11 +29,13 @@
View Articles within …
The App
+ Chrome Custom Tab
The App
The System Browser
- @string/pref_htmlviewer_app
+ - @string/pref_htmlviewer_chromecustomtabs
- @string/pref_htmlviewer_browser