This commit is contained in:
seven332 committed 2016-02-04 00:59:14 +08:00
1 parent f3a7bcbfd3
commit 705dd16541
37 files changed
+624 -597

No files matched your search

@@ -0,0 +1,65 @@
/*
* Copyright 2015 Hippo Seven
*
* 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.
*/
package com.hippo.util;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast;
import com.hippo.ehviewer.R;
public class ActivityHelper {
public static boolean sendEmail(@NonNull Activity from, @NonNull String address,
@Nullable String subject, @Nullable String text) {
Intent i = new Intent(Intent.ACTION_SENDTO);
i.setData(Uri.parse("mailto:" + address));
if (subject != null) {
i.putExtra(Intent.EXTRA_SUBJECT, subject);
}
if (text != null) {
i.putExtra(Intent.EXTRA_TEXT, text);
}
try {
from.startActivity(i);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(from, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
return false;
}
}
public static boolean share(@NonNull Activity from, String text) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
try {
from.startActivity(sendIntent);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(from, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
return false;
}
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.util;
import android.os.Build;
public final class ApiHelper {
public static final boolean SUPPORT_TRANSITION = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
@@ -0,0 +1,167 @@
// 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.
package com.hippo.util;
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 ACTION_CUSTOM_TABS_CONNECTION =
"android.support.customtabs.action.CustomTabsService";
private static final String UNINITIALIZED = "UNINITIALIZED";
private static String sPackageNameToUse = UNINITIALIZED;
private CustomTabsHelper() {}
/**
* The browser of smartisan is terrible
*/
public static String getPackageNameToUseFixed(Context context) {
if (!UNINITIALIZED.equals(sPackageNameToUse)) return sPackageNameToUse;
PackageManager pm = context.getPackageManager();
// Get all apps that can handle VIEW intents.
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
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.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;
} else {
sPackageNameToUse = null;
}
return sPackageNameToUse;
}
/**
* 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;
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.util;
import android.content.Context;
import android.support.annotation.NonNull;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.network.StatusCodeException;
import org.apache.http.conn.ConnectTimeoutException;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public final class ExceptionUtils {
private static final String TAG = ExceptionUtils.class.getSimpleName();
@NonNull
public static String getReadableString(@NonNull Context context, @NonNull Exception e) {
if (e instanceof ConnectTimeoutException ||
e instanceof SocketTimeoutException) {
return context.getString(R.string.error_timeout);
} else if (e instanceof UnknownHostException) {
return context.getString(R.string.error_unknown_host);
} else if (e instanceof StatusCodeException) {
StatusCodeException sce = (StatusCodeException) e;
StringBuilder sb = new StringBuilder();
sb.append(context.getString(R.string.error_bad_status_code, sce.getResponseCode()));
if (sce.isIdentifiedResponseCode()) {
sb.append(", ").append(sce.getMessage());
}
return sb.toString();
} else if (e instanceof ProtocolException && e.getMessage().startsWith("Too many follow-up requests:")) {
return context.getString(R.string.error_redirection);
} else if (e instanceof ProtocolException || e instanceof SocketException) {
return context.getString(R.string.error_socket);
} else if (e instanceof EhException) {
return e.getMessage();
} else {
return context.getString(R.string.error_unknown);
}
}
}
@@ -0,0 +1,178 @@
/*
* Copyright 2015 Hippo Seven
*
* 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.
*/
package com.hippo.util;
import android.content.Context;
import android.content.res.Resources;
import com.hippo.ehviewer.R;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public final class ReadableTime {
private static Resources sResources;
public static final long SECOND_MILLIS = 1000;
public static final long MINUTE_MILLIS = 60 * SECOND_MILLIS;
public static final long HOUR_MILLIS = 60 * MINUTE_MILLIS;
public static final long DAY_MILLIS = 24 * HOUR_MILLIS;
public static final long WEEK_MILLIS = 7 * DAY_MILLIS;
public static final long YEAR_MILLIS = 365 * DAY_MILLIS;
public static final int SIZE = 5;
public static final long[] MULTIPLES = {
YEAR_MILLIS,
DAY_MILLIS,
HOUR_MILLIS,
MINUTE_MILLIS,
SECOND_MILLIS
};
public static final int[] UNITS = {
R.plurals.year,
R.plurals.day,
R.plurals.hour,
R.plurals.minute,
R.plurals.second
};
private static final Calendar sCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+08:00"));
private static final Object sCalendarLock = new Object();
private static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR =
new SimpleDateFormat("MMM d", Locale.getDefault());
private static final SimpleDateFormat DATE_FORMAT_WIT_YEAR =
new SimpleDateFormat("yyy MMM d", Locale.getDefault());
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault());
private static final Object sDateFormatLock1 = new Object();
private static final SimpleDateFormat FILENAMABLE_DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.getDefault());
private static final Object sDateFormatLock2 = new Object();
static {
// The website use GMT+08:00, so tell user the same
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
DATE_FORMAT_WITHOUT_YEAR.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
}
public static void initialize(Context context) {
sResources = context.getApplicationContext().getResources();
}
/*
public static String getDisplayTime(long time) {
if (Settings.getPrettyTime()) {
return getTimeAgo(time);
} else {
return getPlainTime(time);
}
}
*/
public static String getPlainTime(long time) {
synchronized (sDateFormatLock1) {
return DATE_FORMAT.format(new Date(time));
}
}
public static String getTimeAgo(long time) {
Resources resources = sResources;
long now = System.currentTimeMillis();
if (time > now + (2 * MINUTE_MILLIS) || time <= 0) {
return resources.getString(R.string.from_the_future);
}
final long diff = now - time;
if (diff < MINUTE_MILLIS) {
return resources.getString(R.string.just_now);
} else if (diff < 2 * MINUTE_MILLIS) {
return resources.getQuantityString(R.plurals.some_minutes_ago, 1, 1);
} else if (diff < 50 * MINUTE_MILLIS) {
int minutes = (int) (diff / MINUTE_MILLIS);
return resources.getQuantityString(R.plurals.some_minutes_ago, minutes, minutes);
} else if (diff < 90 * MINUTE_MILLIS) {
return resources.getQuantityString(R.plurals.some_hours_ago, 1, 1);
} else if (diff < 24 * HOUR_MILLIS) {
int hours = (int) (diff / HOUR_MILLIS);
return resources.getQuantityString(R.plurals.some_hours_ago, hours, hours);
} else if (diff < 48 * HOUR_MILLIS) {
return resources.getString(R.string.yesterday);
} else if (diff < WEEK_MILLIS) {
int days = (int) (diff / DAY_MILLIS);
return resources.getString(R.string.some_days_ago, days);
} else {
synchronized (sCalendarLock) {
Date nowDate = new Date(now);
Date timeDate = new Date(time);
sCalendar.setTime(nowDate);
int nowYear = sCalendar.get(Calendar.YEAR);
sCalendar.setTime(timeDate);
int timeYear = sCalendar.get(Calendar.YEAR);
if (nowYear == timeYear) {
return DATE_FORMAT_WITHOUT_YEAR.format(timeDate);
} else {
return DATE_FORMAT_WIT_YEAR.format(timeDate);
}
}
}
}
public static String getTimeInterval(long time) {
StringBuilder sb = new StringBuilder();
Resources resources = sResources;
long leftover = time;
boolean start = false;
for (int i = 0; i < SIZE; i++) {
long multiple = MULTIPLES[i];
long quotient = leftover / multiple;
long remainder = leftover % multiple;
if (start || quotient != 0 || i == SIZE - 1) {
if (start) {
sb.append(" ");
}
sb.append(quotient)
.append(" ")
.append(resources.getQuantityString(UNITS[i], (int) quotient));
start = true;
}
leftover = remainder;
}
return sb.toString();
}
public static String getFilenamableTime(long time) {
synchronized (sDateFormatLock2) {
return FILENAMABLE_DATE_FORMAT.format(new Date(time));
}
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.util;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.URLSpan;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class TextUrl {
private static final Pattern URL_PATTERN = Pattern.compile("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/\\*\\.\\?\\+\\$\\[\\]\\(\\)]+)?/?");
public static CharSequence handleTextUrl(CharSequence content) {
Matcher m = URL_PATTERN.matcher(content);
Spannable spannable = null;
while (m.find()) {
// Ensure spannable
if (spannable == null) {
if (content instanceof Spannable) {
spannable = (Spannable) content;
} else {
spannable = new SpannableString(content);
}
}
int start = m.start();
int end = m.end();
URLSpan[] links = spannable.getSpans(start, end, URLSpan.class);
if (links.length > 0) {
// There has been URLSpan already, leave it alone
continue;
}
URLSpan urlSpan = new URLSpan(m.group(0));
spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable == null ? content : spannable;
}
}