diff options
Diffstat (limited to 'main/src/cgeo/geocaching/utils')
20 files changed, 574 insertions, 126 deletions
diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java index 6e59a91..fdd9a9d 100644 --- a/main/src/cgeo/geocaching/utils/AngleUtils.java +++ b/main/src/cgeo/geocaching/utils/AngleUtils.java @@ -1,6 +1,6 @@ package cgeo.geocaching.utils; -public class AngleUtils { +public final class AngleUtils { private AngleUtils() { // Do not instantiate @@ -8,7 +8,7 @@ public class AngleUtils { /** * Return the angle to turn of to go from an angle to the other - * + * * @param from * the origin angle in degrees * @param to diff --git a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java new file mode 100644 index 0000000..7526d92 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -0,0 +1,139 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.activity.Progress; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.os.AsyncTask; + +/** + * AsyncTask which automatically shows a progress dialog. Use it like the {@code AsyncTask} class, but leave away the + * middle template parameter. Override {@link #doInBackgroundInternal(Object[])} and related methods. + * <p> + * If no style is given, the progress dialog uses "determinate" style with known maximum. The progress maximum is + * automatically derived from the number of {@code Params} given to the task in {@link #execute(Object...)}. + * </p> + * + * @param <Params> + * @param <Result> + */ +public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Params, Integer, Result> { + + private final Progress progress = new Progress(); + private final Activity activity; + private final String progressTitle; + private final String progressMessage; + private boolean indeterminate = false; + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) { + this(activity, progressTitle, progressMessage, false); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle) { + this(activity, progressTitle, null); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, boolean indeterminate) { + this.activity = activity; + this.progressTitle = progressTitle; + this.progressMessage = progressMessage; + this.indeterminate = indeterminate; + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) { + this(activity, progressTitle, null, indeterminate); + } + + @Override + protected final void onPreExecute() { + if (null != activity) { + if (indeterminate) { + progress.show(activity, progressTitle, progressMessage, true, null); + } + else { + progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null); + } + } + onPreExecuteInternal(); + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}. + */ + protected void onPreExecuteInternal() { + // empty by default + } + + @Override + protected final void onPostExecute(Result result) { + onPostExecuteInternal(result); + if (null != activity) { + progress.dismiss(); + } + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPostExecute(Object)}. + * + * @param result + */ + protected void onPostExecuteInternal(Result result) { + // empty by default + } + + @Override + protected final void onProgressUpdate(Integer... status) { + final int progressValue = status[0]; + if (null != activity && progressValue >= 0) { + progress.setProgress(progressValue); + } + onProgressUpdateInternal(progressValue); + } + + /** + * This method should by overridden by sub classes instead of {@link #onProgressUpdate(Integer...)}. + */ + protected void onProgressUpdateInternal(@SuppressWarnings("unused") int progress) { + // empty by default + } + + protected void setMessage(final String message) { + progress.setMessage(message); + } + + @Override + protected final Result doInBackground(Params... params) { + if (params != null) { + progress.setMaxProgressAndReset(params.length); + } + return doInBackgroundInternal(params); + } + + protected abstract Result doInBackgroundInternal(Params[] params); +} diff --git a/main/src/cgeo/geocaching/utils/ClipboardUtils.java b/main/src/cgeo/geocaching/utils/ClipboardUtils.java index e6779ad..67069b2 100644 --- a/main/src/cgeo/geocaching/utils/ClipboardUtils.java +++ b/main/src/cgeo/geocaching/utils/ClipboardUtils.java @@ -3,7 +3,6 @@ package cgeo.geocaching.utils; import cgeo.geocaching.cgeoapplication; import android.content.Context; -import android.text.ClipboardManager; /** * Clipboard Utilities. Functions to copy data to the Android clipboard. @@ -13,6 +12,10 @@ import android.text.ClipboardManager; @SuppressWarnings("deprecation") public final class ClipboardUtils { + private ClipboardUtils() { + // utility class + } + /** * Places the text passed in onto the clipboard as text * @@ -20,7 +23,8 @@ public final class ClipboardUtils { * The text to place in the clipboard. */ public static void copyToClipboard(final CharSequence text) { - final ClipboardManager clipboard = (ClipboardManager) cgeoapplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); + // 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); } diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java index df2baa0..18a337d 100644 --- a/main/src/cgeo/geocaching/utils/CryptUtils.java +++ b/main/src/cgeo/geocaching/utils/CryptUtils.java @@ -12,6 +12,10 @@ import javax.crypto.spec.SecretKeySpec; public final class CryptUtils { + private CryptUtils() { + // utility class + } + private static char[] base64map1 = new char[64]; private static byte[] base64map2 = new byte[128]; @@ -37,28 +41,36 @@ public final class CryptUtils { } } + private static class Rot13Encryption { + private boolean plaintext = false; + + char getNextEncryptedCharacter(final char c) { + int result = c; + if (result == '[') { + plaintext = true; + } else if (result == ']') { + plaintext = false; + } else if (!plaintext) { + int capitalized = result & 32; + result &= ~capitalized; + result = ((result >= 'A') && (result <= 'Z') ? ((result - 'A' + 13) % 26 + 'A') : result) + | capitalized; + } + return (char) result; + } + } + public static String rot13(String text) { if (text == null) { return ""; } final StringBuilder result = new StringBuilder(); - // plaintext flag (do not convert) - boolean plaintext = false; + Rot13Encryption rot13 = new Rot13Encryption(); final int length = text.length(); for (int index = 0; index < length; index++) { - int c = text.charAt(index); - if (c == '[') { - plaintext = true; - } else if (c == ']') { - plaintext = false; - } else if (!plaintext) { - int capitalized = c & 32; - c &= ~capitalized; - c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) - | capitalized; - } - result.append((char) c); + char c = text.charAt(index); + result.append(rot13.getNextEncryptedCharacter(c)); } return result.toString(); } @@ -71,7 +83,7 @@ public final class CryptUtils { digest.update(text.getBytes(), 0, text.length()); hashed = new BigInteger(1, digest.digest()).toString(16); } catch (Exception e) { - Log.e("cgBase.md5", e); + Log.e("CryptUtils.md5", e); } return hashed; @@ -85,7 +97,7 @@ public final class CryptUtils { digest.update(text.getBytes(), 0, text.length()); hashed = new BigInteger(1, digest.digest()).toString(16); } catch (Exception e) { - Log.e("cgBase.sha1", e); + Log.e("CryptUtils.sha1", e); } return hashed; @@ -100,7 +112,7 @@ public final class CryptUtils { mac.init(secretKeySpec); macBytes = mac.doFinal(text.getBytes()); } catch (Exception e) { - Log.e("cgBase.hashHmac", e); + Log.e("CryptUtils.hashHmac", e); } return macBytes; @@ -111,22 +123,12 @@ public final class CryptUtils { // a SpannableStringBuilder instead of the pure text and we must replace each character inline. // Otherwise we loose all the images, colors and so on... final SpannableStringBuilder buffer = new SpannableStringBuilder(span); - boolean plaintext = false; + Rot13Encryption rot13 = new Rot13Encryption(); final int length = span.length(); for (int index = 0; index < length; index++) { - int c = span.charAt(index); - if (c == '[') { - plaintext = true; - } else if (c == ']') { - plaintext = false; - } else if (!plaintext) { - int capitalized = c & 32; - c &= ~capitalized; - c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) - | capitalized; - } - buffer.replace(index, index + 1, String.valueOf((char) c)); + char c = span.charAt(index); + buffer.replace(index, index + 1, String.valueOf(rot13.getNextEncryptedCharacter(c))); } return buffer; } diff --git a/main/src/cgeo/geocaching/utils/DateUtils.java b/main/src/cgeo/geocaching/utils/DateUtils.java index fd5ef4a..b148979 100644 --- a/main/src/cgeo/geocaching/utils/DateUtils.java +++ b/main/src/cgeo/geocaching/utils/DateUtils.java @@ -2,7 +2,12 @@ package cgeo.geocaching.utils; import java.util.Calendar; -public class DateUtils { +public final class DateUtils { + + private DateUtils() { + // utility class + } + public static int daysSince(long date) { final Calendar logDate = Calendar.getInstance(); logDate.setTimeInMillis(date); diff --git a/main/src/cgeo/geocaching/utils/EditUtils.java b/main/src/cgeo/geocaching/utils/EditUtils.java index f2f89e7..d975df9 100644 --- a/main/src/cgeo/geocaching/utils/EditUtils.java +++ b/main/src/cgeo/geocaching/utils/EditUtils.java @@ -8,6 +8,10 @@ import android.widget.TextView; public final class EditUtils { + private EditUtils() { + // utility class + } + public static void setActionListener(final EditText editText, final Runnable runnable) { editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { diff --git a/main/src/cgeo/geocaching/utils/FileUtils.java b/main/src/cgeo/geocaching/utils/FileUtils.java index 6fefc02..5ab8fcc 100644 --- a/main/src/cgeo/geocaching/utils/FileUtils.java +++ b/main/src/cgeo/geocaching/utils/FileUtils.java @@ -9,12 +9,16 @@ import java.io.File; import java.util.List; /** - * Utiliy class for files + * Utility class for files * * @author rsudev * */ -public class FileUtils { +public final class FileUtils { + + private FileUtils() { + // utility class + } public static void listDir(List<File> result, File directory, FileSelector chooser, Handler feedBackHandler) { diff --git a/main/src/cgeo/geocaching/utils/GeoDirHandler.java b/main/src/cgeo/geocaching/utils/GeoDirHandler.java index 21b2562..14e6426 100644 --- a/main/src/cgeo/geocaching/utils/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/utils/GeoDirHandler.java @@ -1,18 +1,27 @@ package cgeo.geocaching.utils; import cgeo.geocaching.IGeoData; -import cgeo.geocaching.Settings; import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.settings.Settings; import android.os.Handler; import android.os.Message; /** * GeoData and Direction handler. Manipulating geodata and direction information - * through a GeoDirHandler ensures that all listeners are registered from a - * {@link android.os.Looper} thread. + * through a GeoDirHandler ensures that all listeners are registered from a {@link android.os.Looper} thread. + * <p> + * To use this class, override at least one of {@link #updateDirection(float)} or {@link #updateGeoData(IGeoData)}. You + * need to start the handler using one of + * <ul> + * <li>{@link #startDir()}</li> + * <li>{@link #startGeo()}</li> + * <li>{@link #startGeoAndDir()}</li> + * </ul> + * A good place might be the {@code onResume} method of the Activity. Stop the Handler accordingly in {@code onPause}. + * </p> */ -public class GeoDirHandler extends Handler implements IObserver<Object> { +public abstract class GeoDirHandler extends Handler implements IObserver<Object> { private static final int OBSERVABLE = 1 << 1; private static final int START_GEO = 1 << 2; @@ -54,10 +63,16 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { obtainMessage(OBSERVABLE, o).sendToTarget(); } + public void updateAll() { + update(app.currentGeo()); + update(app.currentDirection()); + } + /** * Update method called when new IGeoData is available. * - * @param data the new data + * @param data + * the new data */ protected void updateGeoData(final IGeoData data) { // Override this in children @@ -66,7 +81,8 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { /** * Update method called when new direction data is available. * - * @param direction the new direction + * @param direction + * the new direction */ protected void updateDirection(final float direction) { // Override this in children @@ -118,4 +134,3 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { sendEmptyMessage(STOP_GEO | STOP_DIR); } } - diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index 8d4eed1..5717a37 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -10,7 +10,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -public class HtmlUtils { +public final class HtmlUtils { + + private HtmlUtils() { + // utility class + } /** * Extract the text from a HTML based string. This is similar to what HTML.fromHtml(...) does, but this method also @@ -20,6 +24,9 @@ public class HtmlUtils { * @return */ public static String extractText(CharSequence html) { + if (StringUtils.isBlank(html)) { + return StringUtils.EMPTY; + } String result = html.toString(); // recognize images in textview HTML contents diff --git a/main/src/cgeo/geocaching/utils/ImageHelper.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index 98cad64..ea7d3ff 100644 --- a/main/src/cgeo/geocaching/utils/ImageHelper.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -8,10 +8,13 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -public class ImageHelper { +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; - // Do not let this class be instantiated, this is a utility class. - private ImageHelper() { +public final class ImageUtils { + + private ImageUtils() { + // Do not let this class be instantiated, this is a utility class. } /** @@ -22,11 +25,21 @@ public class ImageHelper { * @return BitmapDrawable The scaled image */ public static BitmapDrawable scaleBitmapToFitDisplay(final Bitmap image) { - final cgeoapplication app = cgeoapplication.getInstance(); Point displaySize = Compatibility.getDisplaySize(); final int maxWidth = displaySize.x - 25; final int maxHeight = displaySize.y - 25; + return scaleBitmapTo(image, maxWidth, maxHeight); + } + /** + * Scales a bitmap to the given bounds if it is larger, otherwise returns the original bitmap. + * + * @param image + * The bitmap to scale + * @return BitmapDrawable The scaled image + */ + public static BitmapDrawable scaleBitmapTo(final Bitmap image, final int maxWidth, final int maxHeight) { + final cgeoapplication app = cgeoapplication.getInstance(); Bitmap result = image; int width = image.getWidth(); int height = image.getHeight(); @@ -43,4 +56,27 @@ public class ImageHelper { return resultDrawable; } + /** + * Store a bitmap to file. + * + * @param bitmap + * The bitmap to store + * @param format + * The image format + * @param quality + * The image quality + * @param pathOfOutputImage + * Path to store to + */ + 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); + bitmap.compress(format, quality, bos); + bos.flush(); + bos.close(); + } catch (Exception e) { + Log.e("ImageHelper.storeBitmap", e); + } + } } diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index 698d38a..708dff0 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -10,13 +10,13 @@ import java.util.List; /** * Synchronized set wrapper for the LeastRecentlyUsedMap. * - * This code is heavily based on the HashSet code that represent Map as a Set. + * This code is heavily based on the HashSet code that represents Map as a Set. * Unfortunately HashSet does not allow to use a custom Map as its Storage. * Therefore overriding removeEldestEntry() is impossible for a normal LinkedHashSet. * * Synchronization is added to guard against concurrent modification. Iterator * access has to be guarded externally or the synchronized getAsList method can be used - * to get a clone for iteration + * to get a clone for iteration. */ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> implements Cloneable, java.io.Serializable { @@ -28,7 +28,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) { // because we don't use any Map.get() methods from the Set, BOUNDED and LRU_CACHE have the exact same Behaviour - // So we useLRU_CACHE mode because it should perform a bit better (as it doesn't re-add explicitly) + // So we use LRU_CACHE mode because it should perform a bit better (as it doesn't re-add explicitly) map = new LeastRecentlyUsedMap.LruCache<E, Object>(maxEntries, initialCapacity, loadFactor); } diff --git a/main/src/cgeo/geocaching/utils/Log.java b/main/src/cgeo/geocaching/utils/Log.java index 6d57b75..f7f33d9 100644 --- a/main/src/cgeo/geocaching/utils/Log.java +++ b/main/src/cgeo/geocaching/utils/Log.java @@ -2,6 +2,7 @@ package cgeo.geocaching.utils; import android.os.Environment; +import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -22,11 +23,11 @@ final public class Log { } /** - * make a non persisted copy of the debug flag from the settings for performance reasons - * + * save a copy of the debug flag from the settings for performance reasons + * * @param isDebug */ - public static void setDebugUnsaved(boolean isDebug) { + public static void setDebug(boolean isDebug) { Log.isDebug = isDebug; } @@ -100,12 +101,14 @@ final public class Log { first = false; file.delete(); } + Writer writer = null; try { - final Writer writer = new FileWriter(file, true); + writer = new BufferedWriter(new FileWriter(file, true)); writer.write(msg); - writer.close(); } catch (final IOException e) { Log.e("logToFile: cannot write to " + file, e); + } finally { + IOUtils.closeQuietly(writer); } } } diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 0a8e547..98201b5 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -1,12 +1,12 @@ package cgeo.geocaching.utils; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.Settings; import cgeo.geocaching.Trackable; -import cgeo.geocaching.Geocache; -import cgeo.geocaching.connector.gc.GCConstants; -import cgeo.geocaching.connector.gc.Login; -import cgeo.geocaching.network.Network; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.capability.ILogin; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.Formatter; import org.apache.commons.lang3.StringUtils; @@ -14,10 +14,14 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; /** - * provides all the available templates for logging + * Provides all the available templates for logging. * */ -public class LogTemplateProvider { +public final class LogTemplateProvider { + + private LogTemplateProvider() { + // utility class + } /** * Context aware data container for log templates. @@ -40,54 +44,57 @@ public class LogTemplateProvider { this.trackable = trackable; } - public LogContext(boolean offline) { + public LogContext(final boolean offline) { this(null, offline); } - public LogContext(final Geocache cache, boolean offline) { + public LogContext(final Geocache cache, final boolean offline) { this.cache = cache; this.offline = offline; } - public Geocache getCache() { + public final Geocache getCache() { return cache; } - public Trackable getTrackable() { + public final Trackable getTrackable() { return trackable; } - public boolean isOffline() { + public final boolean isOffline() { return offline; } } - public static abstract class LogTemplate { + public abstract static class LogTemplate { private final String template; private final int resourceId; - protected LogTemplate(String template, int resourceId) { + protected LogTemplate(final String template, final int resourceId) { this.template = template; this.resourceId = resourceId; } - abstract public String getValue(LogContext context); + public abstract String getValue(LogContext context); - public int getResourceId() { + public final int getResourceId() { return resourceId; } - public int getItemId() { + public final int getItemId() { return template.hashCode(); } - public String getTemplateString() { + public final String getTemplateString() { return template; } - protected String apply(String input, LogContext context) { - if (input.contains("[" + template + "]")) { - return StringUtils.replace(input, "[" + template + "]", getValue(context)); + protected final String apply(final String input, final LogContext context) { + final String bracketedTemplate = "[" + template + "]"; + + // check containment first to not unconditionally call the getValue(...) method + if (input.contains(bracketedTemplate)) { + return StringUtils.replace(input, bracketedTemplate, getValue(context)); } return input; } @@ -128,20 +135,32 @@ public class LogTemplateProvider { @Override public String getValue(final LogContext context) { - int current = Login.getActualCachesFound(); + final Geocache cache = context.getCache(); + if (cache == null) { + return StringUtils.EMPTY; + } + + int current = 0; + final IConnector connector = ConnectorFactory.getConnector(cache); + if (connector instanceof ILogin) { + current = ((ILogin) connector).getCachesFound(); + } + + // try updating the login information, if the counter is zero if (current == 0) { if (context.isOffline()) { - return ""; + return StringUtils.EMPTY; + } + if (connector instanceof ILogin) { + ((ILogin) connector).login(null, null); + current = ((ILogin) connector).getCachesFound(); } - final String page = Network.getResponseData(Network.getRequest("http://www.geocaching.com/email/")); - current = parseFindCount(page); } - String findCount = ""; if (current >= 0) { - findCount = String.valueOf(current + 1); + return String.valueOf(current + 1); } - return findCount; + return StringUtils.EMPTY; } }); templates.add(new LogTemplate("OWNER", R.string.init_signature_template_owner) { @@ -156,13 +175,13 @@ public class LogTemplateProvider { if (cache != null) { return cache.getOwnerDisplayName(); } - return ""; + return StringUtils.EMPTY; } }); return templates; } - public static LogTemplate getTemplate(int itemId) { + public static LogTemplate getTemplate(final int itemId) { for (LogTemplate template : getTemplates()) { if (template.getItemId() == itemId) { return template; @@ -171,9 +190,9 @@ public class LogTemplateProvider { return null; } - public static String applyTemplates(String signature, LogContext context) { + public static String applyTemplates(final String signature, final LogContext context) { if (signature == null) { - return ""; + return StringUtils.EMPTY; } String result = signature; for (LogTemplate template : getTemplates()) { @@ -181,17 +200,4 @@ public class LogTemplateProvider { } return result; } - - private static int parseFindCount(String page) { - if (StringUtils.isBlank(page)) { - return -1; - } - - try { - return Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")); - } catch (NumberFormatException e) { - Log.e("parseFindCount", e); - return -1; - } - } } diff --git a/main/src/cgeo/geocaching/utils/PeriodicHandler.java b/main/src/cgeo/geocaching/utils/PeriodicHandler.java index 2759580..288bbb0 100644 --- a/main/src/cgeo/geocaching/utils/PeriodicHandler.java +++ b/main/src/cgeo/geocaching/utils/PeriodicHandler.java @@ -3,16 +3,26 @@ package cgeo.geocaching.utils; import android.os.Handler; import android.os.Message; +import java.lang.ref.WeakReference; + /** * A PeriodicHandler class helps with the implementation of a periodic * action embedded in a thread with a looper such as the UI thread. - * The act() method will be called periodically. The clock may drift - * as the implementation does not target real-time actions. + * The onPeriodic() method of the listener will be called periodically. + * The clock may drift as the implementation does not target real-time + * actions. * * The handler will be interrupted if the device goes to sleep. * + * The handler only keeps a weak reference to the listener. If the listener + * is garbage-collected without having stopped the timer, the handler will + * stop itself. */ -abstract public class PeriodicHandler extends Handler { +final public class PeriodicHandler extends Handler { + + public static interface PeriodicHandlerListener { + public void onPeriodic(); + } final static private int START = 0; final static private int STOP = 1; @@ -20,21 +30,19 @@ abstract public class PeriodicHandler extends Handler { final private long period; + final private WeakReference<PeriodicHandlerListener> listenerRef; + /** * Create a new PeriodicHandler object. * * @param period * The period in milliseconds. */ - protected PeriodicHandler(final long period) { + public PeriodicHandler(final long period, final PeriodicHandlerListener listener) { this.period = period; + listenerRef = new WeakReference<PeriodicHandlerListener>(listener); } - /** - * Subclasses of PeriodicHandler must implement this method. - */ - abstract public void act(); - @Override public void handleMessage(final Message msg) { switch (msg.what) { @@ -46,8 +54,11 @@ abstract public class PeriodicHandler extends Handler { removeMessages(ACT); break; case ACT: - sendEmptyMessageDelayed(ACT, period); - act(); + final PeriodicHandlerListener listener = listenerRef.get(); + if (listener != null) { + sendEmptyMessageDelayed(ACT, period); + listener.onPeriodic(); + } break; default: throw new UnsupportedOperationException(); diff --git a/main/src/cgeo/geocaching/utils/ProcessUtils.java b/main/src/cgeo/geocaching/utils/ProcessUtils.java index 53991fb..3345ff1 100644 --- a/main/src/cgeo/geocaching/utils/ProcessUtils.java +++ b/main/src/cgeo/geocaching/utils/ProcessUtils.java @@ -2,15 +2,59 @@ package cgeo.geocaching.utils; import cgeo.geocaching.cgeoapplication; +import org.apache.commons.collections.CollectionUtils; + import android.content.Intent; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; -public class ProcessUtils { +import java.util.List; - public static boolean isInstalled(final String packageName) { +public final class ProcessUtils { + + private ProcessUtils() { + // utility class + } + + /** + * Preferred method to detect the availability of an external app + * + * @param packageName + * @return + */ + public static boolean isLaunchable(final String packageName) { return getLaunchIntent(packageName) != null; } + /** + * Checks whether a launch intent is available or if the package is just installed + * This function is relatively costly, so if you know that the package in question has + * a launch intent, use isLaunchable() instead. + * + * @param packageName + * @return + */ + public static boolean isInstalled(final String packageName) { + return isLaunchable(packageName) || hasPackageInstalled(packageName); + } + + /** + * This will find installed applications even without launch intent (e.g. the streetview plugin). + */ + private static boolean hasPackageInstalled(final String packageName) { + final List<PackageInfo> packs = cgeoapplication.getInstance().getPackageManager().getInstalledPackages(0); + for (final PackageInfo packageInfo : packs) { + if (packageName.equals(packageInfo.packageName)) { + return true; + } + } + return false; + } + + /** + * This will find applications, which can be launched. + */ public static Intent getLaunchIntent(final String packageName) { if (packageName == null) { return null; @@ -20,8 +64,16 @@ public 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 (Exception e) { + } catch (final Exception e) { return null; } } + + public static boolean isIntentAvailable(final String intent) { + final PackageManager packageManager = cgeoapplication.getInstance().getPackageManager(); + final List<ResolveInfo> list = packageManager.queryIntentActivities( + new Intent(intent), PackageManager.MATCH_DEFAULT_ONLY); + + return CollectionUtils.isNotEmpty(list); + } } diff --git a/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java b/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java new file mode 100644 index 0000000..94246e0 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java @@ -0,0 +1,86 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.CacheDetailActivity; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.Progress; + +import android.content.res.Resources; +import android.os.Message; + +import java.lang.ref.WeakReference; + +public class SimpleCancellableHandler extends CancellableHandler { + public static final String SUCCESS_TEXT = "success_message"; + protected final WeakReference<AbstractActivity> activityRef; + protected final WeakReference<Progress> progressDialogRef; + + public SimpleCancellableHandler(final AbstractActivity activity, final Progress progress) { + this.activityRef = new WeakReference<AbstractActivity>(activity); + this.progressDialogRef = new WeakReference<Progress>(progress); + } + + @Override + public void handleRegularMessage(final Message msg) { + AbstractActivity activity = activityRef.get(); + if (activity != null && msg.getData() != null && msg.getData().getString(SUCCESS_TEXT) != null) { + activity.showToast(msg.getData().getString(SUCCESS_TEXT)); + } + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.dismiss(); + } + return; + } + + @Override + public void handleCancel(final Object extra) { + AbstractActivity activity = activityRef.get(); + if (activity != null) { + activity.showToast((String) extra); + } + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + + public final void showToast(int resId) { + AbstractActivity activity = activityRef.get(); + if (activity != null) { + Resources res = activity.getResources(); + activity.showToast(res.getText(resId).toString()); + } + } + + public final void dismissProgress() { + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + + protected final void setProgressMessage(final String txt) { + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.setMessage(txt); + } + } + + protected final void finishActivity() { + AbstractActivity activity = activityRef.get(); + if (activity != null) { + activity.finish(); + } + + } + + protected void updateStatusMsg(final int resId, final String msg) { + CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); + if (activity != null) { + setProgressMessage(activity.getResources().getString(resId) + + "\n\n" + + msg); + } + } + +} diff --git a/main/src/cgeo/geocaching/utils/SimpleHandler.java b/main/src/cgeo/geocaching/utils/SimpleHandler.java new file mode 100644 index 0000000..554ded6 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/SimpleHandler.java @@ -0,0 +1,65 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.Progress; + +import android.content.res.Resources; +import android.os.Handler; +import android.os.Message; + +import java.lang.ref.WeakReference; + +public abstract class SimpleHandler extends Handler { + public static final String SUCCESS_TEXT = "success_message"; + protected final WeakReference<AbstractActivity> activityRef; + protected final WeakReference<Progress> progressDialogRef; + + public SimpleHandler(final AbstractActivity activity, final Progress progress) { + this.activityRef = new WeakReference<AbstractActivity>(activity); + this.progressDialogRef = new WeakReference<Progress>(progress); + } + + @Override + public void handleMessage(final Message msg) { + AbstractActivity activity = activityRef.get(); + if (activity != null && msg.getData() != null && msg.getData().getString(SUCCESS_TEXT) != null) { + activity.showToast(msg.getData().getString(SUCCESS_TEXT)); + } + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.dismiss(); + } + return; + } + + protected final void showToast(final int resId) { + AbstractActivity activity = activityRef.get(); + if (activity != null) { + Resources res = activity.getResources(); + activity.showToast(res.getText(resId).toString()); + } + } + + protected final void dismissProgress() { + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + + protected final void setProgressMessage(final String txt) { + Progress progressDialog = progressDialogRef.get(); + if (progressDialog != null) { + progressDialog.setMessage(txt); + } + } + + protected final void finishActivity() { + AbstractActivity activity = activityRef.get(); + if (activity != null) { + activity.finish(); + } + + } + +} diff --git a/main/src/cgeo/geocaching/utils/BaseUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index 82e48cb..c9d4958 100644 --- a/main/src/cgeo/geocaching/utils/BaseUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -9,10 +9,14 @@ import java.util.regex.Pattern; /** * Misc. utils. All methods don't use Android specific stuff to use these methods in plain JUnit tests. */ -public final class BaseUtils { +public final class TextUtils { private static final Pattern PATTERN_REMOVE_NONPRINTABLE = Pattern.compile("\\p{Cntrl}"); + private TextUtils() { + // utility class + } + /** * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned * @@ -69,7 +73,7 @@ public final class BaseUtils { * @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 BaseUtils.getMatch(data, p, trim, 1, defaultValue, false); + return TextUtils.getMatch(data, p, trim, 1, defaultValue, false); } /** @@ -84,7 +88,7 @@ public final class BaseUtils { * @return defaultValue or the first group if the pattern matches (trimmed) */ public static String getMatch(final String data, final Pattern p, final String defaultValue) { - return BaseUtils.getMatch(data, p, true, 1, defaultValue, false); + return TextUtils.getMatch(data, p, true, 1, defaultValue, false); } /** @@ -104,13 +108,13 @@ public final class BaseUtils { } /** - * Replaces every \n, \r and \t with a single space. Afterwards multiples spaces + * Replaces every \n, \r and \t with a single space. Afterwards multiple spaces * are merged into a single space. Finally leading spaces are deleted. * * This method must be fast, but may not lead to the shortest replacement String. * * You are only allowed to change this code if you can prove it became faster on a device. - * see cgeo.geocaching.test.WhiteSpaceTest#replaceWhitespaceManually in the test project + * see cgeo.geocaching.test.WhiteSpaceTest#replaceWhitespaceManually in the test project. * * @param data * complete HTML page @@ -137,10 +141,11 @@ public final class BaseUtils { } /** - * Quick and naive check for possible html-content of a string. + * Quick and naive check for possible rich HTML content in a string. * - * @param str - * @return True, if <code>str</code> could contain html + * @param str A string containing HTML code. + * @return <tt>true</tt> if <tt>str</tt> contains HTML code that needs to go through a HTML renderer before + * being displayed, <tt>false</tt> if it can be displayed as-is without any loss */ public static boolean containsHtml(final String str) { return str.indexOf('<') != -1 || str.indexOf('&') != -1; diff --git a/main/src/cgeo/geocaching/utils/TranslationUtils.java b/main/src/cgeo/geocaching/utils/TranslationUtils.java index 4d318f0..1224f7e 100644 --- a/main/src/cgeo/geocaching/utils/TranslationUtils.java +++ b/main/src/cgeo/geocaching/utils/TranslationUtils.java @@ -19,6 +19,10 @@ public final class TranslationUtils { public static final int translationTextLengthToWarn = 500; private static final String TRANSLATION_APP = "com.google.android.apps.translate"; + private TranslationUtils() { + // utility class + } + /** * Build a URI for Google Translate * @@ -31,7 +35,7 @@ public final class TranslationUtils { private static String buildTranslationURI(final String toLang, final String text) { String content = Network.encode(text); // the app works better without the "+", the website works better with "+", therefore assume using the app if installed - if (ProcessUtils.isInstalled(TRANSLATION_APP)) { + if (ProcessUtils.isLaunchable(TRANSLATION_APP)) { content = content.replace("+", "%20"); } return translationWebsite + translationForceClassicMode + translationAutoSelect + translationFieldSeparator + toLang + translationFieldSeparator + content; diff --git a/main/src/cgeo/geocaching/utils/XmlUtils.java b/main/src/cgeo/geocaching/utils/XmlUtils.java index 4e08f42..2d85950 100644 --- a/main/src/cgeo/geocaching/utils/XmlUtils.java +++ b/main/src/cgeo/geocaching/utils/XmlUtils.java @@ -4,7 +4,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; -public class XmlUtils { +public final class XmlUtils { private XmlUtils() { // Do not instantiate |
