aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/utils
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/cgeo/geocaching/utils')
-rw-r--r--main/src/cgeo/geocaching/utils/AngleUtils.java43
-rw-r--r--main/src/cgeo/geocaching/utils/ClipboardUtils.java17
-rw-r--r--main/src/cgeo/geocaching/utils/CryptUtils.java44
-rw-r--r--main/src/cgeo/geocaching/utils/Formatter.java65
-rw-r--r--main/src/cgeo/geocaching/utils/HtmlUtils.java28
-rw-r--r--main/src/cgeo/geocaching/utils/ImageUtils.java109
-rw-r--r--main/src/cgeo/geocaching/utils/JsonUtils.java20
-rw-r--r--main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java79
-rw-r--r--main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java7
-rw-r--r--main/src/cgeo/geocaching/utils/ProcessUtils.java6
-rw-r--r--main/src/cgeo/geocaching/utils/RxUtils.java126
-rw-r--r--main/src/cgeo/geocaching/utils/StartableHandlerThread.java80
-rw-r--r--main/src/cgeo/geocaching/utils/TextUtils.java68
13 files changed, 426 insertions, 266 deletions
diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java
index fdd9a9d..5ab2c75 100644
--- a/main/src/cgeo/geocaching/utils/AngleUtils.java
+++ b/main/src/cgeo/geocaching/utils/AngleUtils.java
@@ -1,7 +1,17 @@
package cgeo.geocaching.utils;
+import cgeo.geocaching.CgeoApplication;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.WindowManager;
+
public final class AngleUtils {
+ private static class WindowManagerHolder {
+ public static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
+ }
+
private AngleUtils() {
// Do not instantiate
}
@@ -27,4 +37,37 @@ public final class AngleUtils {
public static float normalize(final float angle) {
return (angle >= 0 ? angle : (360 - ((-angle) % 360))) % 360;
}
+
+ public static int getRotationOffset() {
+ switch (WindowManagerHolder.WINDOW_MANAGER.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_90:
+ return 90;
+ case Surface.ROTATION_180:
+ return 180;
+ case Surface.ROTATION_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Take the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @param direction the unadjusted direction in degrees, in the [0, 360[ range
+ * @return the adjusted direction in degrees, in the [0, 360[ range
+ */
+ public static float getDirectionNow(final float direction) {
+ return normalize(direction + getRotationOffset());
+ }
+
+ /**
+ * Reverse the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @param direction the unadjusted direction in degrees, in the [0, 360[ range
+ * @return the adjusted direction in degrees, in the [0, 360[ range
+ */
+ public static float reverseDirectionNow(final float direction) {
+ return normalize(direction - getRotationOffset());
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/ClipboardUtils.java b/main/src/cgeo/geocaching/utils/ClipboardUtils.java
index 77250f3..fb30886 100644
--- a/main/src/cgeo/geocaching/utils/ClipboardUtils.java
+++ b/main/src/cgeo/geocaching/utils/ClipboardUtils.java
@@ -2,6 +2,8 @@ package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import org.eclipse.jdt.annotation.Nullable;
+
import android.content.Context;
/**
@@ -9,7 +11,6 @@ import android.content.Context;
* This class uses the deprecated function ClipboardManager.setText(CharSequence).
* API 11 introduced setPrimaryClip(ClipData)
*/
-@SuppressWarnings("deprecation")
public final class ClipboardUtils {
private ClipboardUtils() {
@@ -22,10 +23,24 @@ public final class ClipboardUtils {
* @param text
* The text to place in the clipboard.
*/
+ @SuppressWarnings("deprecation")
public static void copyToClipboard(final CharSequence text) {
// fully qualified name used here to avoid buggy deprecation warning (of javac) on the import statement
final android.text.ClipboardManager clipboard = (android.text.ClipboardManager) CgeoApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
+ /**
+ * get clipboard content
+ *
+ */
+ @SuppressWarnings("deprecation")
+ @Nullable
+ public static String getText() {
+ // fully qualified name used here to avoid buggy deprecation warning (of javac) on the import statement
+ final android.text.ClipboardManager clipboard = (android.text.ClipboardManager) CgeoApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE);
+ final CharSequence text = clipboard.getText();
+ return text != null ? text.toString() : null;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java
index 815c2f4..f2ff0c2 100644
--- a/main/src/cgeo/geocaching/utils/CryptUtils.java
+++ b/main/src/cgeo/geocaching/utils/CryptUtils.java
@@ -1,6 +1,5 @@
package cgeo.geocaching.utils;
-
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
@@ -23,28 +22,29 @@ public final class CryptUtils {
// utility class
}
- private static char[] base64map1 = new char[64];
- private static byte[] base64map2 = new byte[128];
+ private static final byte[] EMPTY = {};
+ private static char[] BASE64MAP1 = new char[64];
+ private static byte[] BASE64MAP2 = new byte[128];
static {
int i = 0;
for (char c = 'A'; c <= 'Z'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
for (char c = 'a'; c <= 'z'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
for (char c = '0'; c <= '9'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
- base64map1[i++] = '+';
- base64map1[i++] = '/';
+ BASE64MAP1[i++] = '+';
+ BASE64MAP1[i++] = '/';
- for (i = 0; i < base64map2.length; i++) {
- base64map2[i] = -1;
+ for (i = 0; i < BASE64MAP2.length; i++) {
+ BASE64MAP2[i] = -1;
}
for (i = 0; i < 64; i++) {
- base64map2[base64map1[i]] = (byte) i;
+ BASE64MAP2[BASE64MAP1[i]] = (byte) i;
}
}
@@ -88,9 +88,7 @@ public final class CryptUtils {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(text.getBytes(CharEncoding.UTF_8), 0, text.length());
return new BigInteger(1, digest.digest()).toString(16);
- } catch (NoSuchAlgorithmException e) {
- Log.e("CryptUtils.md5", e);
- } catch (UnsupportedEncodingException e) {
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
Log.e("CryptUtils.md5", e);
}
@@ -98,20 +96,16 @@ public final class CryptUtils {
}
public static byte[] hashHmac(String text, String salt) {
- byte[] macBytes = {};
try {
final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(CharEncoding.UTF_8), "HmacSHA1");
final Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKeySpec);
- macBytes = mac.doFinal(text.getBytes(CharEncoding.UTF_8));
- } catch (GeneralSecurityException e) {
- Log.e("CryptUtils.hashHmac", e);
- } catch (UnsupportedEncodingException e) {
+ return mac.doFinal(text.getBytes(CharEncoding.UTF_8));
+ } catch (GeneralSecurityException | UnsupportedEncodingException e) {
Log.e("CryptUtils.hashHmac", e);
+ return EMPTY;
}
-
- return macBytes;
}
public static CharSequence rot13(final Spannable span) {
@@ -145,11 +139,11 @@ public final class CryptUtils {
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
int o3 = i2 & 0x3F;
- out[op++] = base64map1[o0];
- out[op++] = base64map1[o1];
- out[op] = op < oDataLen ? base64map1[o2] : '=';
+ out[op++] = BASE64MAP1[o0];
+ out[op++] = BASE64MAP1[o1];
+ out[op] = op < oDataLen ? BASE64MAP1[o2] : '=';
op++;
- out[op] = op < oDataLen ? base64map1[o3] : '=';
+ out[op] = op < oDataLen ? BASE64MAP1[o3] : '=';
op++;
}
diff --git a/main/src/cgeo/geocaching/utils/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java
index 3068cd4..c764c5a 100644
--- a/main/src/cgeo/geocaching/utils/Formatter.java
+++ b/main/src/cgeo/geocaching/utils/Formatter.java
@@ -33,7 +33,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatTime(long date) {
+ public static String formatTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME);
}
@@ -45,7 +45,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDate(long date) {
+ public static String formatDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE);
}
@@ -58,7 +58,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatFullDate(long date) {
+ public static String formatFullDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR);
}
@@ -71,8 +71,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDate(long date) {
- DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
+ public static String formatShortDate(final long date) {
+ final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
return dateFormat.format(date);
}
@@ -84,8 +84,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateVerbally(long date) {
- int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ public static String formatShortDateVerbally(final long date) {
+ final int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
switch (diff) {
case 0:
return CgeoApplication.getInstance().getString(R.string.log_today);
@@ -104,7 +104,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateTime(long date) {
+ public static String formatShortDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL);
}
@@ -116,11 +116,11 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDateTime(long date) {
+ public static String formatDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
- public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) {
+ public static String formatCacheInfoLong(final Geocache cache, final CacheListType cacheListType) {
final ArrayList<String> infos = new ArrayList<>();
if (StringUtils.isNotBlank(cache.getGeocode())) {
infos.add(cache.getGeocode());
@@ -137,13 +137,13 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatCacheInfoShort(Geocache cache) {
+ public static String formatCacheInfoShort(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>();
addShortInfos(cache, infos);
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- private static void addShortInfos(Geocache cache, final ArrayList<String> infos) {
+ private static void addShortInfos(final Geocache cache, final ArrayList<String> infos) {
if (cache.hasDifficulty()) {
infos.add("D " + String.format("%.1f", cache.getDifficulty()));
}
@@ -162,7 +162,7 @@ public abstract class Formatter {
}
}
- public static String formatCacheInfoHistory(Geocache cache) {
+ public static String formatCacheInfoHistory(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>(3);
infos.add(StringUtils.upperCase(cache.getGeocode()));
infos.add(Formatter.formatDate(cache.getVisitedDate()));
@@ -170,9 +170,9 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatWaypointInfo(Waypoint waypoint) {
+ public static String formatWaypointInfo(final Waypoint waypoint) {
final List<String> infos = new ArrayList<>(3);
- WaypointType waypointType = waypoint.getWaypointType();
+ final WaypointType waypointType = waypoint.getWaypointType();
if (waypointType != WaypointType.OWN && waypointType != null) {
infos.add(waypointType.getL10n());
}
@@ -188,4 +188,39 @@ public abstract class Formatter {
}
return StringUtils.join(infos, Formatter.SEPARATOR);
}
+
+ public static String formatDaysAgo(final long date) {
+ final int days = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ switch (days) {
+ case 0:
+ return CgeoApplication.getInstance().getString(R.string.log_today);
+ case 1:
+ return CgeoApplication.getInstance().getString(R.string.log_yesterday);
+ default:
+ return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days);
+ }
+ }
+
+ /**
+ * Formatting of the hidden date of a cache
+ *
+ * @param cache
+ * @return {@code null} or hidden date of the cache (or event date of the cache) in human readable format
+ */
+ public static String formatHiddenDate(final Geocache cache) {
+ final Date hiddenDate = cache.getHiddenDate();
+ if (hiddenDate == null) {
+ return null;
+ }
+ final long time = hiddenDate.getTime();
+ if (time <= 0) {
+ return null;
+ }
+ String dateString = Formatter.formatFullDate(time);
+ if (cache.isEventCache()) {
+ dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString;
+ }
+ return dateString;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java
index 51c4d6e..e90b70d 100644
--- a/main/src/cgeo/geocaching/utils/HtmlUtils.java
+++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java
@@ -24,7 +24,7 @@ public final class HtmlUtils {
* @param html
* @return
*/
- public static String extractText(CharSequence html) {
+ public static String extractText(final CharSequence html) {
if (StringUtils.isBlank(html)) {
return StringUtils.EMPTY;
}
@@ -32,13 +32,13 @@ public final class HtmlUtils {
// recognize images in textview HTML contents
if (html instanceof Spanned) {
- Spanned text = (Spanned) html;
- Object[] styles = text.getSpans(0, text.length(), Object.class);
- ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
- for (Object style : styles) {
+ final Spanned text = (Spanned) html;
+ final Object[] styles = text.getSpans(0, text.length(), Object.class);
+ final ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
+ for (final Object style : styles) {
if (style instanceof ImageSpan) {
- int start = text.getSpanStart(style);
- int end = text.getSpanEnd(style);
+ final int start = text.getSpanStart(style);
+ final int end = text.getSpanEnd(style);
removals.add(Pair.of(start, end));
}
}
@@ -47,12 +47,12 @@ public final class HtmlUtils {
Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() {
@Override
- public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) {
+ public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) {
return rhs.getRight().compareTo(lhs.getRight());
}
});
result = text.toString();
- for (Pair<Integer, Integer> removal : removals) {
+ for (final Pair<Integer, Integer> removal : removals) {
result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight());
}
}
@@ -60,4 +60,14 @@ public final class HtmlUtils {
// now that images are gone, do a normal html to text conversion
return Html.fromHtml(result).toString().trim();
}
+
+ public static String removeExtraParagraph(final String html) {
+ if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) {
+ final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim();
+ if (extractText(paragraph).equals(paragraph)) {
+ return paragraph;
+ }
+ }
+ return html;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java
index 739ecc4..c2b7327 100644
--- a/main/src/cgeo/geocaching/utils/ImageUtils.java
+++ b/main/src/cgeo/geocaching/utils/ImageUtils.java
@@ -1,6 +1,7 @@
package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Image;
import cgeo.geocaching.R;
import cgeo.geocaching.compatibility.Compatibility;
@@ -25,6 +26,8 @@ import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
+import android.text.Html;
+import android.text.Html.ImageGetter;
import android.util.Base64;
import android.util.Base64InputStream;
import android.widget.TextView;
@@ -36,8 +39,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
+import java.util.Collection;
import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Set;
public final class ImageUtils {
private static final int[] ORIENTATIONS = new int[] {
@@ -49,6 +55,10 @@ public final class ImageUtils {
private static final int[] ROTATION = new int[] { 90, 180, 270 };
private static final int MAX_DISPLAY_IMAGE_XY = 800;
+ // Images whose URL contains one of those patterns will not be available on the Images tab
+ // for opening into an external application.
+ private final static String[] NO_EXTERNAL = new String[] { "geocheck.org" };
+
private ImageUtils() {
// Do not let this class be instantiated, this is a utility class.
}
@@ -61,7 +71,7 @@ public final class ImageUtils {
* @return BitmapDrawable The scaled image
*/
public static BitmapDrawable scaleBitmapToFitDisplay(@NonNull final Bitmap image) {
- Point displaySize = Compatibility.getDisplaySize();
+ final Point displaySize = Compatibility.getDisplaySize();
final int maxWidth = displaySize.x - 25;
final int maxHeight = displaySize.y - 25;
return scaleBitmapTo(image, maxWidth, maxHeight);
@@ -76,7 +86,7 @@ public final class ImageUtils {
*/
@Nullable
public static Bitmap readAndScaleImageToFitDisplay(@NonNull final String filename) {
- Point displaySize = Compatibility.getDisplaySize();
+ final Point displaySize = Compatibility.getDisplaySize();
// Restrict image size to 800 x 800 to prevent OOM on tablets
final int maxWidth = Math.min(displaySize.x - 25, MAX_DISPLAY_IMAGE_XY);
final int maxHeight = Math.min(displaySize.y - 25, MAX_DISPLAY_IMAGE_XY);
@@ -128,12 +138,12 @@ public final class ImageUtils {
*/
public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) {
try {
- FileOutputStream out = new FileOutputStream(pathOfOutputImage);
- BufferedOutputStream bos = new BufferedOutputStream(out);
+ final FileOutputStream out = new FileOutputStream(pathOfOutputImage);
+ final BufferedOutputStream bos = new BufferedOutputStream(out);
bitmap.compress(format, quality, bos);
bos.flush();
bos.close();
- } catch (IOException e) {
+ } catch (final IOException e) {
Log.e("ImageHelper.storeBitmap", e);
}
}
@@ -152,7 +162,7 @@ public final class ImageUtils {
if (maxXY <= 0) {
return filePath;
}
- Bitmap image = readDownsampledImage(filePath, maxXY, maxXY);
+ final Bitmap image = readDownsampledImage(filePath, maxXY, maxXY);
if (image == null) {
return null;
}
@@ -184,7 +194,7 @@ public final class ImageUtils {
try {
final ExifInterface exif = new ExifInterface(filePath);
orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
- } catch (IOException e) {
+ } catch (final IOException e) {
Log.e("ImageUtils.readDownsampledImage", e);
}
final BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options();
@@ -233,7 +243,7 @@ public final class ImageUtils {
}
// Create a media file name
- String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
+ final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
}
@@ -254,7 +264,7 @@ public final class ImageUtils {
* @return <tt>true</tt> if the URL contains at least one of the patterns, <tt>false</tt> otherwise
*/
public static boolean containsPattern(final String url, final String[] patterns) {
- for (String entry : patterns) {
+ for (final String entry : patterns) {
if (StringUtils.containsIgnoreCase(url, entry)) {
return true;
}
@@ -282,7 +292,7 @@ public final class ImageUtils {
/**
* Decode a base64-encoded string and save the result into a stream.
- *
+ *
* @param inString
* the encoded string
* @param out
@@ -303,15 +313,39 @@ public final class ImageUtils {
}
/**
+ * Add images present in the HTML description to the existing collection.
+ *
+ * @param images a collection of images
+ * @param htmlText the HTML description to be parsed
+ * @param geocode the common title for images in the description
+ */
+ public static void addImagesFromHtml(final Collection<Image> images, final String htmlText, final String geocode) {
+ final Set<String> urls = new LinkedHashSet<>();
+ for (final Image image : images) {
+ urls.add(image.getUrl());
+ }
+ Html.fromHtml(StringUtils.defaultString(htmlText), new ImageGetter() {
+ @Override
+ public Drawable getDrawable(final String source) {
+ if (!urls.contains(source) && canBeOpenedExternally(source)) {
+ images.add(new Image(source, StringUtils.defaultString(geocode)));
+ urls.add(source);
+ }
+ return null;
+ }
+ }, null);
+ }
+
+ /**
* Container which can hold a drawable (initially an empty one) and get a newer version when it
* becomes available. It also invalidates the view the container belongs to, so that it is
* redrawn properly.
*/
- @SuppressWarnings("deprecation")
- public final static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
+ public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
private Drawable drawable;
final private TextView view;
+ @SuppressWarnings("deprecation")
public ContainerDrawable(@NonNull final TextView view) {
this.view = view;
drawable = null;
@@ -324,7 +358,7 @@ public final class ImageUtils {
}
@Override
- public void draw(final Canvas canvas) {
+ public final void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
@@ -337,8 +371,55 @@ public final class ImageUtils {
view.setText(view.getText());
}
- public void updateFrom(final Observable<? extends Drawable> drawableObservable) {
+ public final void updateFrom(final Observable<? extends Drawable> drawableObservable) {
drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this);
}
}
+
+ /**
+ * Image that automatically scales to fit a line of text in the containing {@link TextView}.
+ */
+ public final static class LineHeightContainerDrawable extends ContainerDrawable {
+ private final TextView view;
+
+ public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) {
+ super(view, drawableObservable);
+ this.view = view;
+ }
+
+ @Override
+ public void call(final Drawable newDrawable) {
+ super.call(newDrawable);
+ setBounds(ImageUtils.scaleImageToLineHeight(newDrawable, view));
+ }
+ }
+
+ public static boolean canBeOpenedExternally(final String source) {
+ return !containsPattern(source, NO_EXTERNAL);
+ }
+
+ public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) {
+ final int lineHeight = (int) (view.getLineHeight() * 0.8);
+ final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight();
+ return new Rect(0, 0, width, lineHeight);
+ }
+
+ public static Bitmap convertToBitmap(final Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ // handle solid colors, which have no width
+ int width = drawable.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = drawable.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/JsonUtils.java b/main/src/cgeo/geocaching/utils/JsonUtils.java
new file mode 100644
index 0000000..492e137
--- /dev/null
+++ b/main/src/cgeo/geocaching/utils/JsonUtils.java
@@ -0,0 +1,20 @@
+package cgeo.geocaching.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class JsonUtils {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+ public static final ObjectReader reader = mapper.reader();
+ public static final ObjectWriter writer = mapper.writer();
+
+ public static final JsonNodeFactory factory = new JsonNodeFactory(true);
+
+ private JsonUtils() {
+ // Do not instantiate
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
index a69f427..d4cf16e 100644
--- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
+++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
@@ -20,12 +20,9 @@ import java.util.List;
* access has to be guarded externally or the synchronized getAsList method can be used
* to get a clone for iteration.
*/
-public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
- implements Cloneable, java.io.Serializable {
+public class LeastRecentlyUsedSet<E> extends AbstractSet<E> {
- private static final long serialVersionUID = -1942301031191419547L;
-
- private transient LeastRecentlyUsedMap<E, Object> map;
+ private final LeastRecentlyUsedMap<E, Object> map;
private static final Object PRESENT = new Object();
public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) {
@@ -132,26 +129,6 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
}
/**
- * (synchronized) Clone of the set
- * Copy of the HashSet code if clone()
- *
- * @see HashSet
- */
- @Override
- @SuppressWarnings("unchecked")
- public Object clone() throws CloneNotSupportedException {
- try {
- synchronized (this) {
- final LeastRecentlyUsedSet<E> newSet = (LeastRecentlyUsedSet<E>) super.clone();
- newSet.map = (LeastRecentlyUsedMap<E, Object>) map.clone();
- return newSet;
- }
- } catch (CloneNotSupportedException e) {
- throw new InternalError();
- }
- }
-
- /**
* Creates a clone as a list in a synchronized fashion.
*
* @return List based clone of the set
@@ -160,56 +137,4 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
return new ArrayList<>(this);
}
- /**
- * Serialization version of HashSet with the additional parameters for the custom Map
- *
- * @see HashSet
- */
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- // Write out any hidden serialization magic
- s.defaultWriteObject();
-
- // Write out HashMap capacity and load factor
- s.writeInt(map.initialCapacity);
- s.writeFloat(map.loadFactor);
- s.writeInt(map.getMaxEntries());
-
- // Write out size
- s.writeInt(map.size());
-
- // Write out all elements in the proper order.
- for (final E e : map.keySet()) {
- s.writeObject(e);
- }
- }
-
- /**
- * Serialization version of HashSet with the additional parameters for the custom Map
- *
- * @see HashSet
- */
- @SuppressWarnings("unchecked")
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- // Read in any hidden serialization magic
- s.defaultReadObject();
-
- // Read in HashMap capacity and load factor and create backing HashMap
- final int capacity = s.readInt();
- final float loadFactor = s.readFloat();
- final int maxEntries = s.readInt();
-
- map = new LeastRecentlyUsedMap.LruCache<>(maxEntries, capacity, loadFactor);
-
- // Read in size
- final int size = s.readInt();
-
- // Read in all elements in the proper order.
- for (int i = 0; i < size; i++) {
- E e = (E) s.readObject();
- map.put(e, PRESENT);
- }
- }
-
}
diff --git a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
index 1401542..0c6365c 100644
--- a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
+++ b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
@@ -11,14 +11,12 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
private boolean defaultReplaced = false;
public static boolean activateHandler() {
-
final OOMDumpingUncaughtExceptionHandler handler = new OOMDumpingUncaughtExceptionHandler();
return handler.activate();
}
private boolean activate() {
-
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// replace default handler if that has not been done already
@@ -34,10 +32,8 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
}
public static boolean resetToDefault() {
-
- boolean defaultResetted = false;
-
final UncaughtExceptionHandler unspecificHandler = Thread.getDefaultUncaughtExceptionHandler();
+ boolean defaultResetted = unspecificHandler != null;
if (unspecificHandler instanceof OOMDumpingUncaughtExceptionHandler) {
final OOMDumpingUncaughtExceptionHandler handler = (OOMDumpingUncaughtExceptionHandler) unspecificHandler;
@@ -48,7 +44,6 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
}
private boolean reset() {
-
final boolean resetted = defaultReplaced;
if (defaultReplaced) {
diff --git a/main/src/cgeo/geocaching/utils/ProcessUtils.java b/main/src/cgeo/geocaching/utils/ProcessUtils.java
index d80674b..ce05483 100644
--- a/main/src/cgeo/geocaching/utils/ProcessUtils.java
+++ b/main/src/cgeo/geocaching/utils/ProcessUtils.java
@@ -65,7 +65,7 @@ public final class ProcessUtils {
// This can throw an exception where the exception type is only defined on API Level > 3
// therefore surround with try-catch
return packageManager.getLaunchIntentForPackage(packageName);
- } catch (final Exception e) {
+ } catch (final Exception ignored) {
return null;
}
}
@@ -98,7 +98,9 @@ public final class ProcessUtils {
}
final List<ResolveInfo> list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
- return CollectionUtils.isNotEmpty(list);
+ final List<ResolveInfo> servicesList = packageManager.queryIntentServices(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return CollectionUtils.isNotEmpty(list) || CollectionUtils.isNotEmpty(servicesList);
}
}
diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java
index 241ba78..8bbbbf0 100644
--- a/main/src/cgeo/geocaching/utils/RxUtils.java
+++ b/main/src/cgeo/geocaching/utils/RxUtils.java
@@ -1,23 +1,48 @@
package cgeo.geocaching.utils;
import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Observable.Operator;
import rx.Scheduler;
+import rx.Scheduler.Worker;
+import rx.Subscriber;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action0;
+import rx.functions.Func1;
import rx.observables.BlockingObservable;
import rx.schedulers.Schedulers;
+import rx.subscriptions.Subscriptions;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
public class RxUtils {
- // Utility class, not to be instanciated
- private RxUtils() {}
+ private RxUtils() {
+ // Utility class, not to be instantiated
+ }
public final static Scheduler computationScheduler = Schedulers.computation();
public static final Scheduler networkScheduler = Schedulers.from(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+ private static final HandlerThread looperCallbacksThread =
+ new HandlerThread("Looper callbacks thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+ static {
+ looperCallbacksThread.start();
+ }
+
+ public static final Looper looperCallbacksLooper = looperCallbacksThread.getLooper();
+ public static final Scheduler looperCallbacksScheduler = AndroidSchedulers.handlerThread(new Handler(looperCallbacksLooper));
+ public static final Worker looperCallbacksWorker = looperCallbacksScheduler.createWorker();
+
public static <T> void waitForCompletion(final BlockingObservable<T> observable) {
observable.lastOrDefault(null);
}
@@ -25,4 +50,101 @@ public class RxUtils {
public static void waitForCompletion(final Observable<?>... observables) {
waitForCompletion(Observable.merge(observables).toBlocking());
}
+
+ /**
+ * Subscribe function whose subscription and unsubscription take place on a looper thread.
+ *
+ * @param <T>
+ * the type of the observable
+ */
+ public static abstract class LooperCallbacks<T> implements OnSubscribe<T> {
+
+ final AtomicInteger counter = new AtomicInteger(0);
+ final long stopDelay;
+ final TimeUnit stopDelayUnit;
+ protected Subscriber<? super T> subscriber;
+
+ public LooperCallbacks(final long stopDelay, final TimeUnit stopDelayUnit) {
+ this.stopDelay = stopDelay;
+ this.stopDelayUnit = stopDelayUnit;
+ }
+
+ public LooperCallbacks() {
+ this(0, TimeUnit.SECONDS);
+ }
+
+ @Override
+ final public void call(final Subscriber<? super T> subscriber) {
+ this.subscriber = subscriber;
+ looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (counter.getAndIncrement() == 0) {
+ onStart();
+ }
+ subscriber.add(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (counter.decrementAndGet() == 0) {
+ onStop();
+ }
+ }
+ }, stopDelay, stopDelayUnit);
+ }
+ }));
+ }
+ });
+ }
+
+ abstract protected void onStart();
+
+ abstract protected void onStop();
+ }
+
+ public static <T> Operator<T, T> operatorTakeUntil(final Func1<? super T, Boolean> predicate) {
+ return new Operator<T, T>() {
+ @Override
+ public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
+ return new Subscriber<T>(subscriber) {
+ private boolean done = false;
+
+ @Override
+ public void onCompleted() {
+ if (!done) {
+ subscriber.onCompleted();
+ }
+ }
+
+ @Override
+ public void onError(final Throwable e) {
+ if (!done) {
+ subscriber.onError(e);
+ }
+ }
+
+ @Override
+ public void onNext(final T t) {
+ subscriber.onNext(t);
+ boolean shouldEnd = false;
+ try {
+ shouldEnd = predicate.call(t);
+ } catch (final Throwable e) {
+ done = true;
+ subscriber.onError(e);
+ unsubscribe();
+ }
+ if (shouldEnd) {
+ done = true;
+ subscriber.onCompleted();
+ unsubscribe();
+ }
+ }
+ };
+ }
+ };
+ }
+
}
diff --git a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java b/main/src/cgeo/geocaching/utils/StartableHandlerThread.java
deleted file mode 100644
index 91ab1d0..0000000
--- a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package cgeo.geocaching.utils;
-
-import org.eclipse.jdt.annotation.NonNull;
-import rx.Subscriber;
-import rx.functions.Action0;
-import rx.subscriptions.Subscriptions;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-
-/**
- * Derivated class of {@link android.os.HandlerThread} with an exposed handler and a start/stop mechanism
- * based on subscriptions.
- */
-
-public class StartableHandlerThread extends HandlerThread {
-
- private final static int START = 1;
- private final static int STOP = 2;
-
- static public interface Callback {
- public void start(final Context context, final Handler handler);
- public void stop();
- }
-
- // The handler and the thread are intimely linked, there will be no leak.
- @SuppressLint("HandlerLeak")
- private class StartableHandler extends Handler {
- public StartableHandler() {
- super(StartableHandlerThread.this.getLooper());
- }
-
- @Override
- public void handleMessage(final Message message) {
- if (callback != null) {
- switch (message.what) {
- case START:
- callback.start((Context) message.obj, this);
- break;
- case STOP:
- callback.stop();
- break;
- }
- }
- }
- }
-
- private Handler handler;
- private Callback callback;
-
- public StartableHandlerThread(@NonNull final String name, final int priority, final Callback callback) {
- super(name, priority);
- this.callback = callback;
- }
-
- public StartableHandlerThread(@NonNull final String name, final int priority) {
- this(name, priority, null);
- }
-
- public synchronized Handler getHandler() {
- if (handler == null) {
- handler = new StartableHandler();
- }
- return handler;
- }
-
- public void start(final Subscriber<?> subscriber, final Context context) {
- getHandler().obtainMessage(START, context).sendToTarget();
- subscriber.add(Subscriptions.create(new Action0() {
- @Override
- public void call() {
- getHandler().sendEmptyMessage(STOP);
- }
- }));
- }
-
-}
diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java
index 77aa167..04a9007 100644
--- a/main/src/cgeo/geocaching/utils/TextUtils.java
+++ b/main/src/cgeo/geocaching/utils/TextUtils.java
@@ -27,11 +27,11 @@ public final class TextUtils {
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param trim
* Set to true if the group found should be trim'ed
@@ -44,37 +44,38 @@ public final class TextUtils {
* @return defaultValue or the n-th group if the pattern matches (trimmed if wanted)
*/
@SuppressFBWarnings("DM_STRING_CTOR")
- public static String getMatch(@Nullable final String data, final Pattern p, final boolean trim, final int group, final String defaultValue, final boolean last) {
+ public static String getMatch(@Nullable final String data, final Pattern pattern, final boolean trim, final int group, final String defaultValue, final boolean last) {
if (data != null) {
-
- String result = null;
- final Matcher matcher = p.matcher(data);
-
+ final Matcher matcher = pattern.matcher(data);
if (matcher.find()) {
- result = matcher.group(group);
- }
- if (null != result) {
- final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result);
- result = remover.replaceAll(" ");
+ String result = matcher.group(group);
+ while (last && matcher.find()) {
+ result = matcher.group(group);
+ }
- return trim ? new String(result).trim() : new String(result);
- // Java copies the whole page String, when matching with regular expressions
- // later this would block the garbage collector, as we only need tiny parts of the page
- // see http://developer.android.com/reference/java/lang/String.html#backing_array
- // Thus the creating of a new String via String constructor is necessary here!!
+ if (result != null) {
+ final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result);
+ result = remover.replaceAll(" ");
- // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler!
+ // Some versions of Java copy the whole page String, when matching with regular expressions
+ // later this would block the garbage collector, as we only need tiny parts of the page
+ // see http://developer.android.com/reference/java/lang/String.html#backing_array
+ // Thus the creating of a new String via String constructor is voluntary here!!
+ // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler!
+ return trim ? new String(result).trim() : new String(result);
+ }
}
}
+
return defaultValue;
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param trim
* Set to true if the group found should be trim'ed
@@ -82,38 +83,35 @@ public final class TextUtils {
* Value to return if the pattern is not found
* @return defaultValue or the first group if the pattern matches (trimmed if wanted)
*/
- public static String getMatch(final String data, final Pattern p, final boolean trim, final String defaultValue) {
- return TextUtils.getMatch(data, p, trim, 1, defaultValue, false);
+ public static String getMatch(final String data, final Pattern pattern, final boolean trim, final String defaultValue) {
+ return TextUtils.getMatch(data, pattern, trim, 1, defaultValue, false);
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param defaultValue
* Value to return if the pattern is not found
* @return defaultValue or the first group if the pattern matches (trimmed)
*/
- public static String getMatch(@Nullable final String data, final Pattern p, final String defaultValue) {
- return TextUtils.getMatch(data, p, true, 1, defaultValue, false);
+ public static String getMatch(@Nullable final String data, final Pattern pattern, final String defaultValue) {
+ return TextUtils.getMatch(data, pattern, true, 1, defaultValue, false);
}
/**
- * Searches for the pattern p in the data.
+ * Searches for the pattern pattern in the data.
*
* @param data
- * @param p
- * @return true if data contains the pattern p
+ * @param pattern
+ * @return true if data contains the pattern pattern
*/
- public static boolean matches(final String data, final Pattern p) {
- if (data == null) {
- return false;
- }
+ public static boolean matches(final String data, final Pattern pattern) {
// matcher is faster than String.contains() and more flexible - it takes patterns instead of fixed texts
- return p.matcher(data).find();
+ return data != null && pattern.matcher(data).find();
}
@@ -182,7 +180,7 @@ public final class TextUtils {
*/
public static long checksum(final String input) {
final CRC32 checksum = new CRC32();
- checksum.update(input.getBytes());
+ checksum.update(input.getBytes(CHARSET_UTF8));
return checksum.getValue();
}
}