/**
* Utils.java Created on Oct 19, 2012 Copyright 2013 Michele Bonazza
* <emmepuntobi@gmail.com> This file is part of WhatsHare.
*
* WhatsHare is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* WhatsHare. If not, see <http://www.gnu.org/licenses/>.
*/
package it.mb.whatshare;
import java.util.List;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.net.ConnectivityManager;
import android.util.Log;
import android.view.MotionEvent;
/**
* Contains static methods that are used all over the code base.
*
* @author Michele Bonazza
*
*/
public final class Utils {
private static final int MAX_MESSAGE_LENGTH = 4000;
private static final String TAG = "Whatshare";
private static boolean debuggableFlagChecked = false;
private static boolean debugEnabled = false;
/*
* Static methods only!
*/
private Utils() {
}
/**
* Checks whether the app that's running has the
* <code>android:debuggable</code> flag set, and enables/disables all log
* calls accordingly.
*
* @param context
* the caller activity
*/
public static void checkDebug(Context context) {
if (!debuggableFlagChecked) {
debuggableFlagChecked = true;
PackageManager manager = context.getPackageManager();
try {
PackageInfo info = manager.getPackageInfo(
context.getPackageName(), 0);
debugEnabled = (info.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (NameNotFoundException e) {
e.printStackTrace();
// let's keep debug disabled
}
} // else it's already been checked once, don't check anymore
}
/**
* A function to be executed on all items in a list.
*
* <p>
* This is a verbose Java equivalent of lambda functions; it's verbose
* because you must declare a (private anonymous) class to define the lamba
* function.
*
* <p>
* This is how you would use it:
*
* <blockquote>
*
* <pre>
* List<MyClass> myList = Arrays.asList(new MyClass("one"), new MyClass("two"));
* Utils.executeAll(myList, new LambdaFunction<MyClass>() {
* public void execute(MyClass item) {
* // call one of MyClass methods
* item.foo();
* }
* }
* </pre>
*
* </blockquote> Where of course the {@link LambdaFunction} can be stored to
* be reused several times.
*
* @author Michele Bonazza
*
* @param <T>
* the type of elements in the list on which the lambda function
* must be executed
* @see Utils#callOnAll(List, LambdaFunction)
* @see Utils#callOnAll(Object[], LambdaFunction)
*/
public static interface LambdaFunction<T> {
/**
* The function called on all items in the list.
*
* @param item
* the item onto which the function must be called
*/
public void execute(T item);
}
/**
* Logs the argument message as a debug string.
*
* <p>
* This method splits the message into several in case the message is larger
* than the largest String that LogCat can handle for a single print call.
*
* <p>
* The argument <tt>message</tt> can be a format string, in which case
* {@link String#format(String, Object...)} is called with the same
* parameters passed to this method.
*
* @param message
* the message to be logged
* @param formatParms
* zero or more arguments to fill the format string with, in case
* <tt>message</tt> is a format string
*/
public static void debug(String message, Object... formatParms) {
if (!debugEnabled)
return;
int offset = 0;
if (formatParms.length > 0) {
message = String.format(message, formatParms);
}
while (offset < message.length()) {
Log.d(TAG,
message.substring(
offset,
Math.min(offset + MAX_MESSAGE_LENGTH,
message.length())));
offset += MAX_MESSAGE_LENGTH;
}
}
/**
* Logs the output of {@link #listToString(String, List)} in case it's not
* <code>null</code>.
*
* @param listName
* the name of the list to be logged
* @param list
* the list to be logged
*/
public static void debugList(String listName, List<?> list) {
String out = listToString(listName, list);
if (out != null)
debug(out);
}
/**
* Calls the argument lambda <tt>function</tt> on all items in the argument
* <tt>list</tt>.
*
* @param <T>
* the type of elemente in the list
* @param list
* the list onto which the lambda function must be executed
* @param function
* the function to be executed on all items in the list
* @see LambdaFunction
*/
public static <T> void callOnAll(List<T> list, LambdaFunction<T> function) {
for (T item : list) {
function.execute(item);
}
}
/**
* Calls the argument lambda <tt>function</tt> on all items in the argument
* array.
*
* @param <T>
* the type of elemente in the list
* @param items
* the items onto which the lambda function must be executed
* @param function
* the function to be executed on all items in the list
* @see LambdaFunction
*/
public static <T> void callOnAll(T[] items, LambdaFunction<T> function) {
for (T item : items) {
function.execute(item);
}
}
/**
* Returns the last element in the argument <tt>array</tt>.
*
* <p>
* The last element is the one at index <tt>array.length - 1</tt>, or
* <code>null</code> if <tt>array</tt> is empty or <code>null</code>.
*
* @param <T>
* the type of objects stored into the array
* @param array
* the array from which to retrieve the last element
* @return the last element in the array or <code>null</code> if
* <tt>array</tt> is empty or <code>null</code>
*/
public static <T> T getLast(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[array.length - 1];
}
/**
* Returns the last element in the argument <tt>list</tt>.
*
* <p>
* The last element is the one at index <tt>list.size() - 1</tt>, or
* <code>null</code> if <tt>list</tt> is empty or <code>null</code>.
*
* @param <T>
* the type of objects stored into the list
* @param list
* the list from which to retrieve the last element
* @return the last element in the list or <code>null</code> if
* <tt>list</tt> is empty or <code>null</code>
*/
public static <T> T getLast(List<T> list) {
if (list == null || list.isEmpty()) {
return null;
}
return list.get(list.size() - 1);
}
/**
* Returns a String that is empty in case the argument <tt>string</tt> is
* <code>null</code>, the unmodified <tt>string</tt> otherwise.
*
* @param string
* the string to be checked against <code>null</code>
* @return the empty String if <tt>string</tt> is <code>null</code>, the
* argument <tt>string</tt> unmodified otherwise
*/
public static String nonNull(final String string) {
return string == null ? "" : string;
}
/**
* An equivalent of Python's <code>str.join()</code> function on lists: it
* returns a String which is the concatenation of the strings in the
* argument array. The separator between elements is the string providing
* this method. The separator is appended after the first element and it is
* not after the last element.
*
* @param toJoin
* the separator
* @param list
* a list of <code>Object</code>s on which
* {@link Object#toString()} will be called
* @return the concatenation of String representations of the objects in the
* list
*/
public static String join(String toJoin, Object[] list) {
if (list == null || list.length == 0)
return "";
StringBuilder builder = new StringBuilder();
String delimiter = nonNull(toJoin);
int i = 0;
for (; i < (list.length - 1); i++) {
if (list[i] != null)
builder.append(list[i]);
builder.append(delimiter);
}
builder.append(getLast(list));
return builder.toString();
}
/**
* An equivalent of Python's <code>str.join()</code> function on lists: it
* returns a String which is the concatenation of the strings in the
* argument list. The separator between elements is the string providing
* this method. The separator is appended after the first element and it is
* not after the last element.
*
* @param toJoin
* the separator
* @param list
* a list of <code>Object</code>s on which
* {@link Object#toString()} will be called
* @return the concatenation of String representations of the objects in the
* list
*/
public static String join(String toJoin, List<?> list) {
if (list == null || list.isEmpty())
return "";
StringBuilder builder = new StringBuilder();
String delimiter = nonNull(toJoin);
int i = 0;
for (; i < list.size() - 1; i++) {
if (list.get(i) != null)
builder.append(list.get(i));
builder.append(delimiter);
}
builder.append(getLast(list));
return builder.toString();
}
/**
* An equivalent of Python's <code>str.join()</code> function on lists: it
* returns a String which is the concatenation of the strings in the
* argument list. The separator between elements is the string providing
* this method. The separator is appended after the first element and it is
* not after the last element.
*
* @param toJoin
* the separator
* @param set
* a set of <code>Object</code>s on which
* {@link Object#toString()} will be called
* @return the concatenation of String representations of the objects in the
* set
*/
public static String join(String toJoin, Set<?> set) {
return join(toJoin, set.toArray());
}
/**
* Capitalizes a string.
*
* @param s
* the string to be capitalized
* @return the capitalized string
*/
static String capitalize(String s) {
if (s == null || s.length() == 0)
return "";
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
/**
* Returns whether this device is currently connected to the Internet.
*
* @param context
* the caller activity
* @return <code>true</code> if the Internet is reachable from this device
* at this time
*/
public static boolean isConnectedToTheInternet(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isAvailable()
&& cm.getActiveNetworkInfo().isConnected();
}
/**
* Checks whether <b>any</b> of the provided objects is <code>null</code>.
*
* @param objects
* a number of objects of any kind of which a check against
* <code>null</code> values is performed.
* @return <code>true</code> if at least one of the arguments is
* <code>null</code>.
*/
public static boolean isAnyNull(Object... objects) {
for (Object o : objects) {
if (o == null)
return true;
}
return false;
}
/**
* Checks whether the argument <tt>list</tt> is sorted according to its
* natural ordering.
*
* <p>
* If the list is not sorted this method follows a <i>fail-fast</i> fashion:
* as soon as it finds an out-of-order element it returns <code>false</code>.
*
* @param <T>
* the type of elements in the list
* @param list
* the list to be checked
* @return <code>true</code> if <tt>list</tt> is <code>null</code>, empty,
* or sorted according to its natural ordering
*/
public static <T extends Comparable<? super T>> boolean isSorted(
List<T> list) {
if (list == null || list.isEmpty())
return true;
T last = list.get(0);
T current;
for (int i = 1; i < list.size(); i++) {
current = list.get(i);
if (current.compareTo(last) < 0)
return false;
last = current;
}
return true;
}
/**
* Returns a string structured as such: <blockquote>
*
* <pre>
* listName: item1, item2, item3, ..., itemN
* </pre>
*
* </blockquote> where each <tt>item</tt> is the string returned by calling
* {@link Object#toString()} on elements in the list.
*
* <p>
* If <tt>list</tt> is empty, <code>null</code> is returned.
*
* @param listName
* the name of the argument list
* @param list
* the list to be examined
* @return a String as reported above or <code>null</code> if <tt>list</tt>
* is empty or any of the arguments are <code>null</code>
*/
public static String listToString(String listName, List<?> list) {
if (isAnyNull(listName, list))
return null;
return list.isEmpty() ? null : listName + ": " + join(", ", list);
}
/**
* Returns what <tt>PointF.toString()</tt> should have returned (without the
* initial <tt>"PointF"</tt> that the <tt>toString()</tt> default
* implementation returns).
*
* @param point
* the point of which a String representation must be returned
* @return a String containing the point's coordinates enclosed within
* parentheses, or the string "null point" if <tt>point</tt> is
* <code>null</code>
*/
public static String pointToString(PointF point) {
if (point == null)
return "null point";
return new StringBuilder("(").append(point.x).append(",")
.append(point.y).append(")").toString();
}
/**
* Returns what <tt>Point.toString()</tt> should have returned (without the
* initial <tt>"Point"</tt> that the <tt>toString()</tt> default
* implementation returns).
*
* @param point
* the point of which a String representation must be returned
* @return a String containing the point's coordinates enclosed within
* parentheses, or the string "null point" if <tt>point</tt> is
* <code>null</code>
*/
public static String pointToString(Point point) {
if (point == null)
return "null point";
return new StringBuilder("(").append(point.x).append(",")
.append(point.y).append(")").toString();
}
/**
* Applies the transformations stored in the array of float values to the
* argument list of points.
*
* <p>
* The float array can be obtained starting from a {@link Matrix} object by
* calling <blockquote>
*
* <pre>
* Matrix myMatrix;
* float[] matrixValues = new float[9];
* myMatrix.getValues(matrixValues);
* </pre>
*
* </blockquote>
*
* @param matrixValues
* the values to apply to all points in the list
* @param points
* a list of points to which the transformations in the array
* will be applied
*/
public static void applyMatrix(float[] matrixValues, List<PointF> points) {
// variable names are the same used by Skia library
final float tx = matrixValues[Matrix.MTRANS_X];
final float ty = matrixValues[Matrix.MTRANS_Y];
final float mx = matrixValues[Matrix.MSCALE_X];
final float my = matrixValues[Matrix.MSCALE_Y];
final float kx = matrixValues[Matrix.MSKEW_X];
final float ky = matrixValues[Matrix.MSKEW_Y];
/*
* if rotation: skia messes up with the matrix, so sx and sy actually
* store cosV, rx and ry store -sinV and sinV
*/
for (PointF point : points) {
final float originalY = point.y;
point.y = point.x * ky + (point.y * my) + ty;
point.x = point.x * mx + (originalY * kx) + tx;
}
}
/**
* Encodes the argument <tt>matrix</tt> into a JSON array.
*
* <p>
* Values are cast to <tt>double</tt> because of JSON lack for a primitive
* <tt>float</tt> value.
*
* @param matrix
* the matrix to be encoded
* @return the matrix encoded into a JSON array, or <code>null</code> if
* <tt>matrix</tt> is <code>null</code> or inconsistent (i.e. it
* doesn't contain <tt>float</tt>'s)
*/
public static JSONArray matrixToJson(Matrix matrix) {
if (matrix == null)
return null;
JSONArray array = new JSONArray();
float[] values = new float[9];
matrix.getValues(values);
for (float value : values) {
try {
array.put(value);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
return array;
}
/**
* Decodes a matrix encoded using {@link #matrixToJson(Matrix)} from JSON
* format to a {@link Matrix} object.
*
* @param array
* the encoded matrix
* @return a matrix containing values from the JSON string (probably not
* 100% equal to the original because of the
* <tt>float --> double --> float</tt> conversion) or
* <code>null</code> if <tt>array</tt> is <code>null</code> or
* doesn't contain a matrix
*/
public static Matrix jsonToMatrix(JSONArray array) {
if (array == null)
return null;
float[] values = new float[9];
Matrix matrix = new Matrix();
for (int i = 0; i < array.length(); i++) {
try {
values[i] = (float) array.getDouble(i);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
matrix.setValues(values);
return matrix;
}
/**
* Returns the geometric distance between the two argument points.
*
* @param point1
* the first point
* @param point2
* the second point
* @return the distance between the two points
* @throws NullPointerException
* if any of the argument points is <code>null</code>
*/
public static float getDistance(PointF point1, PointF point2) {
if (point1.x == point2.x)
return Math.abs(point2.y - point1.y);
if (point1.y == point2.y)
return Math.abs(point2.x - point1.x);
return (float) Math.sqrt((Math.pow(point2.x - point1.x, 2) + Math.pow(
point2.y - point1.y, 2)));
}
/**
* Returns a string that represents the symbolic name of the specified
* action such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent
* numeric constant such as "35" if unknown. By Google.
*
* @param action
* The action.
* @return The symbolic name of the specified action.
*/
public static String actionToString(int action) {
switch (action) {
case MotionEvent.ACTION_DOWN:
return "ACTION_DOWN";
case MotionEvent.ACTION_UP:
return "ACTION_UP";
case MotionEvent.ACTION_CANCEL:
return "ACTION_CANCEL";
case MotionEvent.ACTION_OUTSIDE:
return "ACTION_OUTSIDE";
case MotionEvent.ACTION_MOVE:
return "ACTION_MOVE";
case MotionEvent.ACTION_HOVER_MOVE:
return "ACTION_HOVER_MOVE";
case MotionEvent.ACTION_SCROLL:
return "ACTION_SCROLL";
case MotionEvent.ACTION_HOVER_ENTER:
return "ACTION_HOVER_ENTER";
case MotionEvent.ACTION_HOVER_EXIT:
return "ACTION_HOVER_EXIT";
}
int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
return "ACTION_POINTER_DOWN(" + index + ")";
case MotionEvent.ACTION_POINTER_UP:
return "ACTION_POINTER_UP(" + index + ")";
default:
return Integer.toString(action);
}
}
}