/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.android;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Handler;
import android.provider.MediaStore;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video.VideoColumns;
import android.telephony.TelephonyManager;
import android.text.TextPaint;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.DatePicker;
import com.mapzen.tangram.LngLat;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.database.TAssert;
import com.rareventure.gps2.database.cache.AreaPanel;
//import com.google.android.maps.MapView;
public class Util {
public static final long MS_PER_YEAR = 1000l * 3600 * 24 * 365;
public static final long MS_PER_DAY = 1000l * 3600 * 24;
public static final long MS_PER_WEEK = MS_PER_DAY * 7;
public static final long MS_PER_HOUR = 1000l * 3600;
public static final int MIN_LONM = -180 * 1000000;
public static final int MAX_LONM = 180 * 1000000 - 1;
public static final int MIN_LON = -180;
public static final int MAX_LON = 180;
public static final int LON_PER_WORLD = 360;
public static void determineMaxBounds(TextPaint tp, Rect bounds, String s) {
Rect bounds2 = new Rect();
tp.getTextBounds(s, 0, s.length(), bounds2);
bounds.union(bounds2);
}
public static int getTextLength(TextPaint tp, String s) {
Rect bounds = new Rect();
tp.getTextBounds(s, 0, s.length(), bounds);
return bounds.right - bounds.left;
}
public static int readInt(InputStream is) throws IOException {
byte [] data = new byte[4];
Util.readFully(is, data);
return byteArrayToInt(data, 0);
}
/**
* Gets an id that is unique to the device
*/
public static String getDeviceId(Context context)
{
final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String tmDevice, tmSerial, androidId;
tmDevice = "" + tm.getDeviceId();
tmSerial = "" + tm.getSimSerialNumber();
androidId = "" + android.provider.Settings.Secure.getString(context.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDevice.hashCode() << 32) | tmSerial.hashCode());
String deviceId = deviceUuid.toString();
return deviceId;
}
// public static void zoomSmoothlyToSpan(MapView mapView, int latSpanMicroDegrees, int lonSpanMicroDegrees,
// float widthPaddingPerc, float heightPaddingPerc) {
// if(latSpanMicroDegrees > mapView.getLatitudeSpan() * (1-heightPaddingPerc) ||
// lonSpanMicroDegrees > mapView.getLongitudeSpan() * (1-widthPaddingPerc))
// {
// do {
// //zoomout. if we are at the farthest zoom level, exit
// if(!mapView.getController().zoomOut())
// return;
// }
// while(latSpanMicroDegrees > mapView.getLatitudeSpan() * (1-heightPaddingPerc)
// || lonSpanMicroDegrees > mapView.getLongitudeSpan() * (1-widthPaddingPerc));
// }
// else {
// do {
// //zoomin. if we are at the closest zoom level, exit
// if(!mapView.getController().zoomIn())
// return;
// }
// while(latSpanMicroDegrees < mapView.getLatitudeSpan() * (1-heightPaddingPerc)
// && lonSpanMicroDegrees < mapView.getLongitudeSpan() * (1-widthPaddingPerc));
//
// //I know it's weird to zoom in a bunch of times and then zoom out
// //but if I invoke zoomToSpan, it will be jumpy, not smooth, and
// //the final zoom out doesn't seem to hurt the animation any
// //(and there doesn't seem to be a better way to do this)
// mapView.getController().zoomOut();
// }
// }
public static int getMaximumWidth(TextPaint textPaint, Object[] units) {
int maxWidth = 0;
for(Object text : units)
{
int width = Util.getTextLength(textPaint, text.toString());
if(width > maxWidth)
maxWidth = width;
}
return maxWidth;
}
public static int measureWithPreferredSize(int measureSpec, int preferredSizeWithPadding) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
return specSize;
}
if (specMode == MeasureSpec.AT_MOST) {
return Math.min(preferredSizeWithPadding, specSize);
}
//specMode == MeasureSpec.UNSPECIFIED
return preferredSizeWithPadding;
}
public static float length(float x,float y)
//average error of about 0.005%
//similar error as the 'rounding error' of 32bit floating point)
//from http://www.osix.net/modules/article/?id=770
{
float absX = x < 0 ? -x : x;
float absY = y < 0 ? -y : y;
float a,b;
if(absX>absY)
{
a=absX;
b=absY;
}
else
{
b=absX;
a=absY;
}
a=a+0.42f*b*b/a;
return (a+(x*x+y*y)/a)/2;
}
public static String varReplace(String data, String ... varName_varValue) {
//PERF: a little slow
for(int i = 0; i < varName_varValue.length; i+=2)
{
data = data.replaceAll("\\$\\{"+varName_varValue[i]+"\\}", varName_varValue[i+1]);
}
return data;
}
public static int intToByteArray2(int value, byte [] data, int start) {
data[start++] = (byte)(value >>> 24);
data[start++] = (byte)(value >>> 16);
data[start++] = (byte)(value >>> 8);
data[start++] = (byte)(value);
return start;
}
public static void writeInt(OutputStream out, int value) throws IOException {
out.write((byte)(value >>> 24));
out.write((byte)(value >>> 16));
out.write((byte)(value >>> 8));
out.write((byte)value);
}
public static final int byteArrayToInt(byte [] b, int start) {
return ((b[start++] )<< 24)
+ ((b[start++] & 0xFF) << 16)
+ ((b[start++] & 0xFF) << 8)
+ (b[start] & 0xFF);
}
/**
* data must have at least 8 bytes from start
*/
public static int longToByteArray2(long value, byte [] data, int start) {
data[start++] = (byte)(value >>> 56);
data[start++] = (byte)(value >>> 48);
data[start++] = (byte)(value >>> 40);
data[start++] = (byte)(value >>> 32);
data[start++] = (byte)(value >>> 24);
data[start++] = (byte)(value >>> 16);
data[start++] = (byte)(value >>> 8);
data[start++] = (byte)(value);
return start;
}
public static final double byteArrayToDouble(byte [] b, int s)
{
return Double.longBitsToDouble(byteArrayToLong(b, s));
}
public static final long byteArrayToLong(byte [] b, int s) {
return ((long)b[s++] << 56)
+ ( (((long)b[s++]) & 0xFF) << 48)
+ (( ((long)b[s++]) & 0xFF) << 40)
+ (( ((long)b[s++]) & 0xFF) << 32)
+ (( ((long)b[s++]) & 0xFF) << 24)
+ ((b[s++] & 0xFF) << 16)
+ ((b[s++] & 0xFF) << 8)
+ ((b[s++] & 0xFF));
}
public static void main(String []argv) throws Exception
{
byte [] salt = new byte[5];
SecretKeySpec skeySpec = new SecretKeySpec(Crypt.getRawKey("my secret","salt".getBytes()), "AES");
Cipher encryptCipher = Cipher.getInstance("AES");
encryptCipher.init(Cipher.ENCRYPT_MODE, skeySpec);
Cipher decryptCipher = Cipher.getInstance("AES");
decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",5,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",Integer.MIN_VALUE,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",Integer.MAX_VALUE,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",-1,-1);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",-1,Integer.MAX_VALUE);
System.out.println("repeat!");
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",5,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",Integer.MIN_VALUE,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",Integer.MAX_VALUE,Integer.MIN_VALUE);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",-1,-1);
testEncryptDecrypt(encryptCipher, decryptCipher, "foo",-1,Integer.MAX_VALUE);
System.out.println("longs");
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",5,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",Long.MIN_VALUE,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",Long.MAX_VALUE,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",-1,-1);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",-1,Long.MAX_VALUE);
System.out.println("repeat!");
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",5,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",Long.MIN_VALUE,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",Long.MAX_VALUE,Long.MIN_VALUE);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",-1,-1);
testEncryptDecryptLong(encryptCipher, decryptCipher, "foo",-1,Long.MAX_VALUE);
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
return result;
}
public static String toHex(byte[] data, int pos, int length) {
if (data.length <= pos)
return "";
StringBuffer result = new StringBuffer(2 * data.length);
for (int i = pos; i < pos+length && i < data.length; i++) {
appendHex(result, data[i]);
}
return result.toString();
}
public static String toHex(byte[] buf) {
return toHex(buf,0,buf.length);
}
private final static String HEX = "0123456789ABCDEF";
public static final int LONM_PER_WORLD = 360 * 1000 * 1000;
public static final float LATM_TO_METERS = 1000000f / 111131.75f ;
public static final float LONM_TO_METERS_AT_EQUATOR = 1000000f / 111131.75f;
public static final float LON_TO_METERS_AT_EQUATOR = 1f / 111131.75f;
public static final int MAX_LATM = 180*1000*1000-1;
public static final int MIN_LATM = -180*1000*1000;
public static final int LATM_PER_WORLD = 180 * 1000 * 1000;
public static final int SECONDS_IN_DAY = 3600 * 24;
public static final int SECONDS_IN_YEAR = SECONDS_IN_DAY*365;
public static final int SECONDS_IN_MONTH = SECONDS_IN_DAY*30;
private static final int MILLIS_IN_MINUTE = 1000*60;
private static final int MILLIS_IN_HOUR = MILLIS_IN_MINUTE * 60;
private static final int MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
private static final long MILLIS_IN_MONTH = 30l * MILLIS_IN_DAY;
private static final long MILLIS_IN_YEAR = 365l * MILLIS_IN_DAY;
private static final double EARTH_RADIUS_M = 6378100;
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
private static void testEncryptDecrypt(Cipher c, Cipher d, String string, int key, int value)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
byte [] data = new byte[3+4+4];
byte [] output = new byte[c.getOutputSize(data.length)];
byte [] result = new byte[3+4+4];
byte [] salt = string.getBytes();
System.arraycopy(salt, 0, data, 0, salt.length);
intToByteArray2(key, data, 3);
intToByteArray2(value, data, 7);
c.doFinal(data,0,data.length,output);
d.doFinal(output,0,output.length,result);
System.out.println("in: "+toHex(data));
System.out.println("enc: "+toHex(output));
System.out.println("dec: "+toHex(result));
for(int i = 0; i < data.length; i++)
{
if(data[i] != result[i])
throw new IllegalStateException("darn");
}
}
private static void testEncryptDecryptLong(Cipher c, Cipher d, String string, long key, long value)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
byte [] data = new byte[3+8+8];
byte [] output = new byte[c.getOutputSize(data.length)];
byte [] result = new byte[3+8+8];
byte [] salt = string.getBytes();
System.arraycopy(salt, 0, data, 0, salt.length);
longToByteArray2(key, data, 3);
longToByteArray2(value, data, 11);
c.doFinal(data,0,data.length,output);
d.doFinal(output,0,output.length,result);
System.out.println("in: "+toHex(data));
System.out.println("enc: "+toHex(output));
System.out.println("dec: "+toHex(result));
for(int i = 0; i < data.length; i++)
{
if(data[i] != result[i])
throw new IllegalStateException("darn gosh");
}
}
public static int doubleToByteArray2(double val, byte[] output, int start) {
return longToByteArray2(Double.doubleToLongBits(val), output, start);
}
public static int floatToByteArray2(float val, byte[] output, int start) {
return intToByteArray2(Float.floatToIntBits(val), output, start);
}
public static String rot13(String s) {
StringBuffer r= new StringBuffer(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 'a' && c <= 'm') c += 13;
else if (c >= 'n' && c <= 'z') c -= 13;
else if (c >= 'A' && c <= 'M') c += 13;
else if (c >= 'A' && c <= 'Z') c -= 13;
r.append(c);
}
return r.toString();
}
public static boolean isByteArray(Class<?> type) {
return type.isArray() && type.getComponentType() == byte.class;
}
public static boolean isIntArray(Class<? extends Object> type) {
return type.isArray() && type.getComponentType() == int.class;
}
public static float square(float i) {
return i*i;
}
public static int normalizeLonm(int lonm)
{
return ((lonm+180000000)%360000000+360000000)%360000000 - 180000000;
}
public static double normalizeLonm(double lonm) {
if(lonm < Util.MIN_LONM)
lonm += Util.LONM_PER_WORLD;
if(lonm > Util.MAX_LONM)
lonm -= Util.LONM_PER_WORLD;
return lonm;
}
/**
* Given a lon coordinate, returns a lon coordinate where if the items
* are subtracted and a distance obtained, it won't wrap the the earth.
* i.e. if the ref lonm is -179,000,000 and lonm is 179,000,000 then
* -181,000,000 will be returned, so that if the items are subtacted, then
* 2,000,000 will be returned, rather than 358,000,000
*/
public static int makeContinuousLonm(int refLonm, int lonm) {
//-358 % 360 is -358
//-718 % 360 is -358
int dist = (lonm - refLonm) % 360000000;
if(dist > 180000000)
{
dist -= 360000000;
}
else if(dist < -180000000)
{
dist += 360000000;
}
return refLonm + dist;
}
/**
* Given a start lon coordinate and a target lon coordinate, returns a lon coordinate
* which is always greater than the start.
*/
public static int makeContinuousFromStartLonm(int startLonm, int lonm) {
//-358 % 360 is -358
//-718 % 360 is -358
return ((lonm - startLonm) % 360000000 + 360000000) % 360000000 + startLonm;
}
public static double makeContinuousLonm(double lonmRef, double lonm) {
double sx = lonm - lonmRef;
//handle lonm wrapping
if(sx > Util.LONM_PER_WORLD>>1)
sx -= Util.LONM_PER_WORLD;
else if(sx < -Util.LONM_PER_WORLD>>1)
sx += Util.LONM_PER_WORLD;
return lonmRef + sx;
}
/**
* Determines whether two lon distances overlap
*/
public static boolean isLonmOverlaps(int lonmStart, int lonmEnd, int lonmStart2, int lonmEnd2) {
lonmEnd = Util.makeContinuousFromStartLonm(lonmStart, lonmEnd);
lonmStart2 = Util.makeContinuousLonm(lonmStart, lonmStart2);
lonmEnd2 = Util.makeContinuousFromStartLonm(lonmStart2, lonmEnd2);
return lonmStart2 <= lonmEnd && lonmEnd2 > lonmStart;
}
public static String toIntList(int[] value) {
StringBuffer sb = new StringBuffer();
for(int v : value)
{
sb.append(v).append(',');
}
if(sb.length() > 0)
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
public static int [] fromStringIntListToIntArray(String value) {
String [] resultS = value.split(",");
int [] result = new int[resultS.length];
for(int i = 0; i < result.length; i++)
result[i] = Integer.parseInt(resultS[i]);
return result;
}
public static int getLonmMin(int l1, int l2) {
int al1 = Util.makeContinuousLonm(l2, l1);
return normalizeLonm(Math.min(al1, l2));
}
public static int getLonmMax(int l1, int l2) {
int al1 = Util.makeContinuousLonm(l2, l1);
return normalizeLonm(Math.max(al1, l2));
}
public static int subtractLonm(int maxLonm, int minLonm) {
return Util.makeContinuousFromStartLonm(minLonm, maxLonm) - minLonm;
}
public static boolean overlaps(int minLatm1, int heightLatm1, int minLatm2, int heightLatm2,
int minLonm1, int widthLonm1, int minLonm2,
int widthLonm2) {
return minLatm1 < minLatm2 + heightLatm2 && minLatm1 + heightLatm1 > minLatm2
&& isLonmOverlaps(minLonm1, widthLonm1, minLonm2, widthLonm2);
}
public static boolean isLonmOverlapsPoint(int lonmStart, int lonmEnd, int lonm) {
return makeContinuousFromStartLonm(lonmStart, lonmEnd) > Util.makeContinuousFromStartLonm(lonmStart, lonm);
}
public static String gnuPlot2DIt(double ... data) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < data.length; i+=2)
{
sb.append(data[i]).append(" ");
sb.append(data[i+1]).append("\n");
}
sb.append("\n");
return sb.toString();
}
public static String gnuPlot3DIt(double ... data) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < data.length; i+=3)
{
//skip cases where the same point is visited twice (it really freaks out gnuplot)
if(i >= 3
&& data[i] == data[i-3]
&& data[i+1] == data[i-2]
&& data[i+2] == data[i-1])
continue;
sb.append(data[i]).append(" ");
sb.append(data[i+1]).append(" ");
sb.append(data[i+2]).append("\n");
}
sb.append("\n");
return sb.toString();
}
/**
* 7 8
* 5 6
*
*
* 3 4
* 1 2
* _
* /|
* /
* latm
*
* lonm ------>
*
* /|\
* |
* time
*/
public static String gnuPlot3DLopsidedBox(int minLatm, int minLonm, int heightLatm,
int widthLonm, long startTime, long endTime,
int endMinLatm,
int endMinLonm) {
return Util.gnuPlot3DIt(minLatm,minLonm,startTime //1
,minLatm,minLonm+widthLonm,startTime //2
,endMinLatm,endMinLonm+widthLonm,endTime //6
,endMinLatm,endMinLonm,endTime //5
,minLatm,minLonm,startTime //1
,minLatm+heightLatm,minLonm,startTime //3
,endMinLatm+heightLatm,endMinLonm,endTime //7
,endMinLatm,endMinLonm,endTime //5
,endMinLatm,endMinLonm+widthLonm,endTime //6
,endMinLatm+heightLatm,endMinLonm+widthLonm,endTime //8
,minLatm+heightLatm,minLonm+widthLonm,startTime //4
,minLatm,minLonm+widthLonm,startTime //2
,minLatm+heightLatm,minLonm+widthLonm,startTime //4
,minLatm+heightLatm,minLonm,startTime //3
,endMinLatm+heightLatm,endMinLonm,endTime //7
,endMinLatm+heightLatm,endMinLonm+widthLonm,endTime); //8
}
/**
* 7 8
* 5 6
*
*
* 3 4
* 1 2
* _
* /|
* /
* latm
*
* lonm ------>
*
* /|\
* |
* time
*/
public static String gnuPlot3DSpaceTimeBox(int minLatm, int minLonm, int heightLatm,
int widthLonm, long startTime, long endTime) {
return Util.gnuPlot3DIt(minLatm,minLonm,startTime //1
,minLatm,minLonm+widthLonm,startTime //2
,minLatm,minLonm+widthLonm,endTime //6
,minLatm,minLonm,endTime //5
,minLatm,minLonm,startTime //1
,minLatm+heightLatm,minLonm,startTime //3
,minLatm+heightLatm,minLonm,endTime //7
,minLatm,minLonm,endTime //5
,minLatm,minLonm+widthLonm,endTime //6
,minLatm+heightLatm,minLonm+widthLonm,endTime //8
,minLatm+heightLatm,minLonm+widthLonm,startTime //4
,minLatm,minLonm+widthLonm,startTime //2
,minLatm+heightLatm,minLonm+widthLonm,startTime //4
,minLatm+heightLatm,minLonm,startTime //3
,minLatm+heightLatm,minLonm,endTime //7
,minLatm+heightLatm,minLonm+widthLonm,endTime); //8
}
// public static boolean connectableToGoogleMaps(Context context) {
// ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
// }
public static String convertToSingleLine(String foo) {
return foo.replace('\n', '~');
}
public static double getDist(double lonm1, double latm1,
double lonm2, double latm2) {
lonm2 = Util.makeContinuousLonm(lonm1, lonm2) - lonm1;
latm2 = latm2 - latm1;
return Math.sqrt(lonm2*lonm2 + latm2*latm2);
}
public static double getDistSquared(double lonm1, double latm1,
double lonm2, double latm2) {
lonm2 = Util.makeContinuousLonm(lonm1, lonm2) - lonm1;
latm2 = latm2 - latm1;
return lonm2*lonm2 + latm2*latm2;
}
/**
* Either does a floor or ceiling to roundTo
* @param val
* @param roundTo
* @param ceil
* @return
*/
public static int granularize(int val, int roundTo, boolean ceil) {
if(roundTo == 0)
return val;
int mod = val % roundTo;
if(ceil)
return val + mod;
return val - mod;
}
public static int maxAll(int ... vals) {
int max = vals [0];
for(int i = 1; i < vals.length; i++)
if(vals[i] > max) max = vals[i];
return max;
}
/**
* @return x where x is the minimum value where 2 ** x > i, x >= 0
*/
public static int minIntegerLog2(long i) {
if(i < 0)
throw new IllegalStateException("WHA??? "+i);
int res = 0;
while(i != 0)
{
i = i >> 1;
res ++;
}
return res;
}
public static int readFully(RandomAccessFile raf, byte [] buffer) throws IOException
{
return readFully(raf, buffer, 0, buffer.length);
}
public static int readFully(RandomAccessFile raf, byte[] buffer, int offset, int length) throws IOException {
int totalRead = 0;
while (totalRead < length) {
int numRead = raf.read(buffer, offset + totalRead, length - totalRead);
if (numRead < 0) {
break;
}
totalRead += numRead;
}
return totalRead;
}
public static int readFully(InputStream raf, byte [] buffer) throws IOException
{
return readFully(raf, buffer, 0, buffer.length);
}
public static int readFully(InputStream raf, byte[] buffer, int offset, int length) throws IOException {
int totalRead = 0;
while (totalRead < length) {
int numRead = raf.read(buffer, offset + totalRead, length - totalRead);
if (numRead < 0) {
break;
}
totalRead += numRead;
}
return totalRead;
}
public static float convertSpToPixel(float sp,Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = sp * metrics.scaledDensity;
return px;
}
public static float convertPixelsToSp(float px,Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return px / metrics.scaledDensity;
}
/**
* This method convets dp unit to equivalent device specific value in pixels.
*
* @param dp A value in dp(Device independent pixels) unit. Which we need to convert into pixels
* @param context Context to get resources and device specific display metrics
* @return A float value to represent Pixels equivalent to dp according to device
*/
public static float convertDpToPixel(float dp,Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * metrics.density;
return px;
}
/**
* This method converts device specific pixels to device independent pixels.
*
* @param px A value in px (pixels) unit. Which we need to convert into db
* @param context Context to get resources and device specific display metrics
* @return A float value to represent db equivalent to px value
*/
public static float convertPixelsToDp(float px,Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / metrics.density;
return dp;
}
public static int parseIntIfPresent(String val, int defaultValue) {
if(val == null || val.length() == 0)
return defaultValue;
return Integer.parseInt(val);
}
public static long parseLongIfPresent(String val, int defaultValue) {
if(val == null || val.length() == 0)
return defaultValue;
return Long.parseLong(val);
}
//2010:05:27 22:24:46
public static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
static
{
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
/**
* Parses the exif date string in the UTC timezone (which is probably not correct, but
* the local timezone may not be either
*/
public static long getExifDateInUTC(ExifInterface ei) {
String dateTimeStr = ei.getAttribute(ExifInterface.TAG_DATETIME);
if(dateTimeStr != null)
{
synchronized (utcFormat)
{
try {
return utcFormat.parse(dateTimeStr).getTime();
}
catch(ParseException e)
{
return 0;
}
}
}
return 0;
}
public static double getExifDouble(ExifInterface ei, String tag, double defaultValue) {
String value = ei.getAttribute(tag);
if(value == null)
return defaultValue;
try {
return Double.parseDouble(value);
}
catch(NumberFormatException e)
{
return defaultValue;
}
}
public static boolean isLonLatSane(double lon, double lat) {
return lon >= -180 && lon < 180 && lat >= -90 && lat <= 90;
}
public static void viewMediaInGallery(
Activity activity, String filename, boolean isImage) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filename)), isImage ? "image/*" :
"video/*");
activity.startActivity(intent);
}
public static Bitmap getBitmap(Context context, int id, boolean isImage)
{
String filename = getDataFilepathForMedia(context.getContentResolver(), id, isImage);
/* ttt_installer:remove_line */Log.d(GTG.TAG,"Loading bitmap for "+filename);
if(filename == null)
return null;
if(isImage)
return new BitmapDrawable(context.getResources(), filename).getBitmap();
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(filename);
return mmr.getFrameAtTime();
}
public static String getMimeTypeForMedia(ContentResolver cr, int id,
boolean isImage) {
Cursor cursor;
if(isImage)
cursor = cr.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Images.Media.MIME_TYPE}, ImageColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
else
cursor = cr.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Video.Media.MIME_TYPE}, VideoColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
try {
if(!cursor.moveToFirst())
return null;
return cursor.getString(0);
}
finally {
cursor.close();
}
}
public static String getDataFilepathForMedia(ContentResolver cr, int id, boolean isImage)
{
Cursor cursor;
if(isImage)
cursor = cr.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Images.Media.DATA}, ImageColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
else
cursor = cr.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Video.Media.DATA}, VideoColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
try {
if(!cursor.moveToFirst())
return null;
return cursor.getString(0);
}
finally {
cursor.close();
}
}
/**
* True if the media exists, false otherwise
* @param context
* @param id
* @param isImage false == video
* @return
*/
public static boolean mediaExists(Context context, int id, boolean isImage) {
ContentResolver cr = context.getContentResolver();
Cursor cursor;
if(isImage)
cursor = cr.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Images.Media.DATA}, ImageColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
else
cursor = cr.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String [] {MediaStore.Video.Media.DATA}, VideoColumns._ID+" = ?",
new String [] { String.valueOf(id) }, null);
try {
if(!cursor.moveToFirst())
return false;
if(!new File(cursor.getString(0)).exists())
return false;
}
finally {
cursor.close();
}
return true;
}
public static void clearCalendarValuesUnder(Calendar calendar, int calendarId) {
switch (calendarId)
{
case Calendar.YEAR:
calendar.set(Calendar.MONTH, calendar.getActualMinimum(Calendar.MONTH));
//no break intentional
case Calendar.MONTH:
calendar.set(Calendar.DATE, calendar.getActualMinimum(Calendar.DATE));
//no break intentional
case Calendar.DATE:
calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMinimum(Calendar.HOUR_OF_DAY));
//no break intentional
case Calendar.HOUR_OF_DAY:
case Calendar.HOUR:
calendar.set(Calendar.MINUTE, calendar.getActualMinimum(Calendar.MINUTE));
//no break intentional
case Calendar.MINUTE:
calendar.set(Calendar.SECOND, calendar.getActualMinimum(Calendar.SECOND));
calendar.set(Calendar.MILLISECOND, calendar.getActualMinimum(Calendar.MILLISECOND));
break;
default:
throw new IllegalStateException("What is "+calendarId);
}
}
public static void printAllStackTraces() {
for(Entry<Thread, StackTraceElement[]> ent : Thread.getAllStackTraces().entrySet())
{
Log.e(GTG.TAG,"Thread "+ent.getKey()+": trace: "+Arrays.toString(ent.getValue()));
}
Log.e(GTG.TAG,"------");
}
/**
* Makes a rect for cross hairs. Assumes width of cross hairs is 1
* @param x
* @param y
* @param crossHairLength
* @return
*/
public static Rect makeRectForCrossHairs(int x, int y,
int crossHairLength) {
return new Rect(x - crossHairLength, y - crossHairLength,
x+crossHairLength+1, y+crossHairLength+1);
}
public static void updateDatePicker(DatePicker datePicker,
long timeMs, boolean subtractOneDay) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeMs);
if(subtractOneDay)
calendar.add(Calendar.DATE, -1);
datePicker.updateDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH));
}
public static long getTimeMsFromDatePicker(DatePicker datePicker, boolean addOneDay) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, datePicker.getYear());
calendar.set(Calendar.MONTH, datePicker.getMonth());
calendar.set(Calendar.DATE, datePicker.getDayOfMonth());
clearCalendarValuesUnder(calendar, Calendar.DATE);
if(addOneDay)
calendar.add(Calendar.DATE, 1);
return calendar.getTimeInMillis();
}
public static boolean localeIsMetric() {
String countryCode = Locale.getDefault().getCountry();
// USA, Liberia, or Burma is imperial
if ("US".equals(countryCode) ||
"LR".equals(countryCode) ||
"MM".equals(countryCode))
return false;
return true;
}
/**
* For View.onMeasure, chooses a measurement that is at least the given value,
* unless overridden by measure spec
*/
public static int chooseAtLeastForOnMeasure(int minValue, int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY || specMode == MeasureSpec.AT_MOST && minValue > specSize)
return specSize;
return minValue;
}
/**
* Calculates difference in full value units. (ie if c1 is
* August 1st, 2011 and c2 is July 31st, 2012, the difference
* in years is 0, months is 11, days is 364, etc., but
* if c2 is August 1st, 2012, then the diff is
* 1 year, or 12 months, or 365 days)
*
* @param c1
* @param nowMs
* @param id
* @return
*/
public static int calcDiff(Calendar c1, long nowMs, int id) {
int presumedDiff;
int step = 1;
switch (id) {
case Calendar.YEAR:
// divide by a impossibly short year, so we can get an estimate
presumedDiff = (int) ((nowMs - c1.getTimeInMillis()) / (Util.MILLIS_IN_YEAR - Util.MILLIS_IN_DAY * 3));
break;
case Calendar.MONTH:
presumedDiff = (int) ((nowMs - c1.getTimeInMillis()) / (Util.MILLIS_IN_MONTH - Util.MILLIS_IN_DAY * 3));
break;
case Calendar.WEEK_OF_MONTH:
case Calendar.WEEK_OF_YEAR:
presumedDiff = (int) ((nowMs - c1.getTimeInMillis()) / (Util.MILLIS_IN_DAY * 7 - Util.MILLIS_IN_HOUR * 2));
step = 7;
id = Calendar.DATE;
break;
case Calendar.DATE:
presumedDiff = (int) ((nowMs - c1.getTimeInMillis()) / (Util.MILLIS_IN_HOUR * 23 - Util.MILLIS_IN_MINUTE * 30));
break;
case Calendar.HOUR:
case Calendar.HOUR_OF_DAY:
return (int) ((nowMs - c1.getTimeInMillis()) / Util.MILLIS_IN_HOUR);
case Calendar.MINUTE:
return (int) ((nowMs - c1.getTimeInMillis()) / Util.MILLIS_IN_MINUTE);
default:
throw new IllegalStateException("What... is " + id + "?");
}
return calcDiff2(c1, nowMs, presumedDiff, id, step);
}
private static int calcDiff2(Calendar c1, long nowMs, int presumedDiff, int id, int step) {
//if its shorter than our minimum estimate
if(presumedDiff < 1)
{
return 0;
}
long oldTimeInMillis = c1.getTimeInMillis();
try {
//so now we need to verify that we were right.
c1.add(id, presumedDiff * step);
while(c1.getTimeInMillis() > nowMs)
{
presumedDiff --;
c1.add(id, -step);
}
return presumedDiff;
}
finally
{
c1.setTimeInMillis(oldTimeInMillis);
}
}
public static boolean isCallable(Context context, Intent intent) {
List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(
intent, 0);
return list.size() > 0;
}
public static String unobfuscate(byte [] data) {
int v = 0;
int s = data[0];
StringBuffer sb = new StringBuffer();
sb.append((char)(data[1] ^ s ^ 42));
for(int i = 2; i < data.length; i++)
{
sb.append((char)(v = data[i] ^ data[i-1] ^ 42 ^ s));
}
return sb.toString();
}
/**
* Runs runnable on ui and waits for it to finish. If the current thread is
* the ui thread, just runs it
*/
public static void runOnUiThreadSynchronously(Activity a, final Runnable runnable) {
final boolean done [] = new boolean[1];
a.runOnUiThread(new Runnable()
{
@Override
public void run() {
runnable.run();
synchronized (done) {
done[0] = true;
done.notify();
}
}
});
synchronized(done)
{
while(!done[0])
{
try {
done.wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
}
/**
* Runs runnable on handler and waits for it to finish. If the current thread is
* the handler thread, just runs it.
*/
public static void runOnHandlerSynchronously(Handler h, final Runnable runnable) {
final boolean done [] = new boolean[1];
if(h.getLooper().getThread() == Thread.currentThread())
{
runnable.run();
}
else {
h.postAtFrontOfQueue(new Runnable()
{
@Override
public void run() {
runnable.run();
synchronized (done) {
done[0] = true;
done.notify();
}
}
});
synchronized(done)
{
while(!done[0])
{
try {
done.wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
}
}
/**
*
* @param in
* @param out
* @return
* @throws IOException if there is an error reading from in
* @return exception if exception occurs when writing
*/
public static IOException copy(byte [] buffer, InputStream in, OutputStream out) throws IOException
{
int read;
while ((read = in.read(buffer)) != -1)
{
try {
out.write(buffer, 0, read);
}
catch (IOException e)
{
return e;
}
}
return null;
}
/**
* Creates a zip file an opens a single entry for writing
* @param filePath
* @param zipEntry
* @return
* @throws IOException
*/
public static ZipOutputStream createZipOutputStream(String filePath,
String zipEntryName) throws IOException {
OutputStream os = new FileOutputStream(filePath);
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
ZipEntry entry = new ZipEntry(zipEntryName);
zos.putNextEntry(entry);
return zos;
}
public static double calcDistFromLonmLatm(double lonm1, double latm1, double lonm2,
double latm2) {
double lon1R = lonm1 * .000001 /180 * Math.PI;
double lat1R = latm1 * .000001 /180 * Math.PI;
double lon2R = lonm2 * .000001 /180 * Math.PI;
double lat2R = latm2 * .000001 /180 * Math.PI;
// from http://www.movable-type.co.uk/scripts/latlong.html
// var R = 6371; // km
// var dLat = (lat2-lat1).toRad();
// var dLon = (lon2-lon1).toRad();
// var lat1 = lat1.toRad();
// var lat2 = lat2.toRad();
//
// var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
// Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
// var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
// var d = R * c;
double dLatR = lat2R - lat1R;
double dLonR = lon2R - lon1R;
double a = Math.sin(dLatR/2) * Math.sin(dLatR/2) +
Math.sin(dLonR/2) * Math.sin(dLonR/2) * Math.cos(lat1R) * Math.cos(lat2R);
double c = 2* Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return EARTH_RADIUS_M * c;
}
public static String[] gmtTimeZoneNames;
public static TimeZone androidPuntTimeZone;
public static final Pattern TIME_ZONE_EXTRA_PATTERN = Pattern.compile("GMT[-+]?\\d\\d?(:?\\d\\d?)?");
public static TimeZone parseTimeZone(String tzId) {
tzId = tzId.trim();
TimeZone tz = TimeZone.getTimeZone(tzId);
//getTimeZone returns GMT if it doesn't understand tzId, so if it does return
// GMT, we need to check if it's lying to us
if(gmtTimeZoneNames == null)
{
gmtTimeZoneNames = TimeZone.getAvailableIDs(0);
//use a dummy value to get the "punt" timezone
androidPuntTimeZone = TimeZone.getTimeZone("android y u so weird");
}
if(tz.hasSameRules(androidPuntTimeZone)
&& Arrays.binarySearch(gmtTimeZoneNames, 0, gmtTimeZoneNames.length, tzId) < 0
&& !TIME_ZONE_EXTRA_PATTERN.matcher(tzId).matches())
{
return null; //I think android punted
}
return tz;
}
public static TimeZone getCurrTimeZone() {
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone();
return tz;
}
/**
* mapzens MapController sometimes returns longitudes outside of -180/+180
* so we convert it to a normal value. Alters given lngLat and returns it.
*/
public static LngLat normalizeLngLat(LngLat p1) {
if(p1.longitude < Util.MIN_LON || p1.longitude> Util.MAX_LON) {
int wraps = (int) Math.floor((p1.longitude - Util.MIN_LON) / Util.LON_PER_WORLD);
p1.longitude -= wraps * Util.LON_PER_WORLD;
}
return p1;
}
public static interface LongComparator<T>
{
public int compare(T obj, long key);
}
public static <T> int binarySearch(List<T> list, long key, LongComparator<T> c) {
int size = list.size();
int low = 0;
int high = size - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
T midVal = list.get(mid);
int cmp = c.compare(midVal, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
private static final int MAX_MENTIONED_ITEMS = 3;
private static class TimeLabel
{
long timeInMillis;
String label, pluralLabel;
public TimeLabel(long timeInMillis, String label, String pluralLabel) {
super();
this.timeInMillis = timeInMillis;
this.label = label;
this.pluralLabel = pluralLabel;
}
public long getValue(long l) {
return l/timeInMillis;
}
public long getRemainder(long l) {
return l%timeInMillis;
}
public void appendText(StringBuffer res, long v) {
if(res.length() != 0)
res.append(", ");
res.append(v).append(" ");
if(v != 1)
res.append(pluralLabel);
else
res.append(label);
}
}
private static TimeLabel[] timeLabels =
{
new TimeLabel(MILLIS_IN_YEAR, "year", "years"),
new TimeLabel(MILLIS_IN_MONTH, "month", "months"),
new TimeLabel(MILLIS_IN_DAY * 7, "week", "weeks"),
new TimeLabel(MILLIS_IN_DAY, "day", "days"),
new TimeLabel(MILLIS_IN_HOUR, "hour", "hours"),
new TimeLabel(MILLIS_IN_MINUTE, "minute", "minutes"),
new TimeLabel(1000, "second", "seconds")
};
public static String convertMsToText(long l) {
int mentionedItems = 0;
StringBuffer res = new StringBuffer();
for(int i = 0; i < timeLabels.length; i++)
{
TimeLabel tl = timeLabels[i];
long v = tl.getValue(l);
l = tl.getRemainder(l);
if(v != 0)
{
tl.appendText(res, v);
if(++mentionedItems >= MAX_MENTIONED_ITEMS)
break;
}
}
if(mentionedItems == 0)
return "--";
return res.toString();
}
public static String doubleToHex(double d) {
byte [] out = new byte[8];
doubleToByteArray2(d, out, 0);
return toHex(out);
}
public static double hexToDouble(String s) {
byte[] b = toByte(s);
return byteArrayToDouble(b, 0);
}
/**
* Runs runnable when the views getWidth() will not return 0
* (after it is layed out)
*/
public static void runWhenGetWidthWorks(final View view, final Runnable runnable) {
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
runnable.run();
}
});
}
}
/**
* Reads a reader line by line into an output string, applying all patterns and replacing
* with given replacements.
* @throws IOException
*/
public static String readReaderIntoStringWithMatchReplace(BufferedReader reader, Pattern [] patterns, String [] replacements) throws IOException
{
if(patterns.length != replacements.length)
TAssert.fail();
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
for(int i = 0; i < patterns.length; i++)
{
line = patterns[i].matcher(line).replaceAll(replacements[i]);
}
sb.append(line).append('\n');
}
return sb.toString();
}
public static void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory())
for (File child : fileOrDirectory.listFiles())
deleteRecursive(child);
fileOrDirectory.delete();
}
}