package com.kedzie.vbox.app;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.kedzie.vbox.BuildConfig;
import com.kedzie.vbox.R;
import com.kedzie.vbox.api.IMachine;
import com.kedzie.vbox.api.IMedium;
/**
* Android Utilities
*
* @apiviz.stereotype utility
*/
public class Utils {
private static final String TAG = "Utils";
/**
* Provides default implementations for {@link AnimationListener}
*/
public static class AnimationAdapter implements AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
/**
* Provides default implementations for {@link TextWatcher}
*/
public static abstract class TextAdapter implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
}
}
/**
* Provides default implementations for {@link OnItemSelectedListener}
*/
public static abstract class OnItemSelectedAdapter implements OnItemSelectedListener {
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
public static boolean isIceCreamSandwhich() {
return isVersion(Build.VERSION_CODES.ICE_CREAM_SANDWICH);
}
public static boolean isJellyBean() {
return isVersion(Build.VERSION_CODES.JELLY_BEAN);
}
/**
* Check if API level satisfies the minimum API level
*
* @param versionCode minimum API version code
* @return <code>true</code> if API level matches
*/
public static boolean isVersion(int versionCode) {
return Build.VERSION.SDK_INT >= versionCode;
}
/**
* Get integer value from default shared preferences
*
* @param context the context
* @param name preference key
* @return The integer value
*/
public static int getIntPreference(Context context, String name) {
return Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(context).getString(name, "0"));
}
/**
* Get boolean value from default shared preferences
*
* @param context the context
* @param name preference key
* @return The boolean value
*/
public static boolean getBooleanPreference(Context context, String name) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(name, false);
}
/**
* Get string value from default shared preferences
*
* @param context the context
* @param name preference key
* @return The string value
*/
public static String getStringPreference(Context context, String name) {
return PreferenceManager.getDefaultSharedPreferences(context).getString(name, "");
}
/**
* Show {@link Toast} long notification with {@link String#format}
*
* @param context message {@link Context}
* @param message Message to show
* @param params String formatting parameters
*/
public static void toastLong(Context context, String message, Object... params) {
toast(context, Toast.LENGTH_LONG, message, params);
}
/**
* Show {@link Toast} short notification
*
* @param context message {@link Context}
* @param message Message to show
* @param params String formatting parameters
*/
public static void toastShort(Context context, String message, Object... params) {
toast(context, Toast.LENGTH_SHORT, message, params);
}
private static void toast(Context context, int length, String message, Object... params) {
Toast.makeText(context, isEmpty(params) ? message : String.format(message, params), length).show();
}
/**
* Check if String is <code>null</code> or empty string
*
* @param s the string
* @return <code>true</code> if empty string or null, <code>false</code>
* otherwise
*/
public static boolean isEmpty(String s) {
return s == null || s.equals("");
}
/**
* Check if array is <code>null</code> or empty
*
* @param array the array
* @return <code>true</code> if empty or null, <code>false</code> otherwise
*/
public static boolean isEmpty(Object[] array) {
return array == null || array.length == 0;
}
/**
* Check if list is <code>null</code> or empty
*
* @param list the array
* @return <code>true</code> if empty or null, <code>false</code> otherwise
*/
public static boolean isEmpty(List<?> list) {
return list == null || list.isEmpty();
}
/**
* Make a nice multiline printout of map contents.
*
* @param title title to show above contents
* @param map the data
* @return string representation
*/
public static String toString(String title, Map<?, ?> map) {
StringBuffer buf = new StringBuffer(title).append("\n=========================\n{");
for (Map.Entry<?, ?> entry : map.entrySet())
buf.append("\n").append(entry.getKey()).append(" ==> ").append(entry.getValue());
return buf.append("}").toString();
}
/**
* Append an element to a comma separated string
*
* @param base base string
* @param append appended element
* @return base string with appended element, with comma if needed
*/
public static StringBuffer appendWithComma(StringBuffer base, String append) {
if (base.length() > 0)
base.append(", ");
return base.append(append);
}
/**
* Get type generic type parameter
* @param genericType the generic {@link Type}
* @param index parameter index
* @return type parameter
*/
public static Class<?> getTypeParameter(Type genericType, int index) {
if (!(genericType instanceof ParameterizedType))
return null;
return (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[index];
}
/**
* Search an array for a specific type of annotation
*
* @param clazz the class
* @param a type of annotation
* @return the annotation or <code>null</code> if not found
*/
public static <T extends Annotation> T getAnnotation(Class<T> clazz, Annotation[] annotations) {
for (Annotation a : annotations)
if (a.annotationType().equals(clazz))
return clazz.cast(a);
return null;
}
/**
* Create an {@link IntentFilter} for multiple actions
*
* @param actions accepted actions
* @return the {@link IntentFilter}
*/
public static IntentFilter createIntentFilter(String... actions) {
IntentFilter filter = new IntentFilter();
for (String action : actions)
filter.addAction(action);
return filter;
}
/**
* Convert DPI to pixels
*
* @param dpi dpi value
* @return equivilent pixel value
*/
public static int dpiToPx(Context context, int dpi) {
return (int) (context.getResources().getDisplayMetrics().density * dpi + .5f);
}
/**
* Specify fragment transition animations
*
* @param tx the transaction
* @return the transaction
*/
public static FragmentTransaction setCustomAnimations(FragmentTransaction tx) {
return tx.setCustomAnimations(com.kedzie.vbox.R.animator.flip_left_in, com.kedzie.vbox.R.animator.flip_left_out,
com.kedzie.vbox.R.animator.flip_right_in, com.kedzie.vbox.R.animator.flip_right_out);
}
/**
* Show a DialogFragment with Back Stack
*
* @param manager {@link FragmentManager}
* @param tag tag for {@link Fragment}
* @param dialog the {@link DialogFragment} implementation
*/
public static void showDialog(FragmentManager manager, String tag, DialogFragment dialog) {
FragmentTransaction tx = manager.beginTransaction().addToBackStack(null);
Fragment prev = manager.findFragmentByTag(tag);
if (prev != null)
tx.remove(prev);
dialog.show(tx, tag);
}
/**
* Detach an fragment
*
* @param manager fragment manager
* @param tx existing transaction
* @param tag tag of existing fragment
*/
public static void detachFragment(FragmentManager manager, FragmentTransaction tx, String tag) {
Fragment existing = manager.findFragmentByTag(tag);
if (existing != null)
tx.detach(existing);
}
/**
* Detach an fragment
*
* @param manager fragment manager
* @param tx existing transaction
* @param id container id
*/
public static void detachFragment(FragmentManager manager, FragmentTransaction tx, int id) {
Fragment existing = manager.findFragmentById(id);
if (existing != null)
tx.detach(existing);
}
/**
* Add new or attach existing Fragment
*
* @param context context
* @param manager fragment manager
* @param containerId container
* @param info fragment definition
*/
public static void addOrAttachFragment(Context context, FragmentManager manager, int containerId, FragmentElement element) {
FragmentTransaction tx = manager.beginTransaction();
addOrAttachFragment(context, manager, tx, containerId, element);
tx.commit();
}
/**
* Instantiate new or attach existing Fragment
*
* @param context context
* @param manager fragment manager
* @param tx existing transaction
* @param containerId container
* @param info fragment definition
*/
public static void addOrAttachFragment(Context context, FragmentManager manager, FragmentTransaction tx, int containerId, FragmentElement element) {
if (element.fragment == null)
element.fragment = manager.findFragmentByTag(element.name);
if (element.fragment == null) {
Log.v("FragmentManager", "Instantiated new Fragment: " + element.name);
tx.add(containerId, element.instantiate(context), element.name);
} else {
Log.v("FragmentManager", "Reattaching existing Fragment: " + element.name);
tx.attach(element.fragment);
}
}
/**
* Remove existing fragment with same tag and add new one.
*
* @param context context
* @param manager fragment manager
* @param containerId container
* @param info fragment definition
*/
public static void replaceFragment(Context context, FragmentManager manager, int containerId, FragmentElement element) {
FragmentTransaction tx = manager.beginTransaction();
replaceFragment(context, manager, tx, containerId, element);
tx.commit();
}
/**
* Remove existing fragment with same tag and add new one.
*
* @param context context
* @param manager fragment manager
* @param tx existing transaction
* @param containerId container
* @param element fragment definition
*/
public static void replaceFragment(Context context, FragmentManager manager, FragmentTransaction tx, int containerId, FragmentElement element) {
Fragment existing = manager.findFragmentByTag(element.name);
if (existing != null)
tx.remove(existing);
Log.d("FragmentManager", "Instantiated new Fragment: " + element.name);
tx.add(containerId, element.instantiate(context), element.name);
}
/**
* Set the contents of a {@link TextView}
*
* @param parent parent view
* @param id textview id
* @param text text contents
*/
public static void setTextView(View parent, int id, String text) {
((TextView) parent.findViewById(id)).setText(text);
}
/**
* Set the contents of a {@link TextView}
*
* @param parent parent view
* @param id textview id
* @param text text contents
*/
public static void setTextView(View parent, int id, int text) {
setTextView(parent, id, text + "");
}
/**
* Set the contents of a {@link TextView}
*
* @param holder view holder
* @param id textview id
* @param text text contents
*/
public static void setTextView(SparseArray<View> holder, int id, String text) {
((TextView) holder.get(id)).setText(text);
}
/**
* Set the contents of a {@link TextView}
*
* @param holder view holder
* @param id textview id
* @param text text contents
*/
public static void setTextView(SparseArray<View> holder, int id, int text) {
setTextView(holder, id, text+"");
}
/**
* Set the contents of a {@link ImageView}
*
* @param parent parent view
* @param id textview id
* @param image contents
*/
public static void setImageView(View parent, int id, Drawable image) {
((ImageView) parent.findViewById(id)).setImageDrawable(image);
}
/**
* Set the contents of a {@link ImageView}
*
* @param parent parent view
* @param id textview id
* @param image contents
*/
public static void setImageView(View parent, int id, Bitmap image) {
((ImageView) parent.findViewById(id)).setImageBitmap(image);
}
/**
* Perform a {@link Thread#sleep} and ignore any
* {@link InterruptedException}
*/
public static void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
}
/**
* Get screen layout size
*
* @param config the {@link Configuration}
* @return screen size
*/
public static int getScreenSize(Configuration config) {
return config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
}
/**
* Find an element within an array
*
* @param array the array
* @param object the object to find
* @return the index of the object in array, or -1 if not found
*/
public static int indexOf(Object[] array, Object object) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(object))
return i;
}
return -1;
}
/**
* Remove "NULL" element of a <code>enum</code> array
*
* @param array array of <code>enum.values()</code>
* @return array with the "NULL" element removed
*/
@SuppressWarnings("unchecked")
public static <T> T[] removeNull(T[] array) {
T[] ret = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1);
int retIndex = 0;
for (int i = 0; i < array.length; i++) {
if (!array[i].toString().equals("Null"))
ret[retIndex++] = array[i];
}
return ret;
}
/**
* Cache commonly used Medium properties
*
* @param m
*/
public static void cacheProperties(IMedium medium) {
synchronized (medium) {
medium.getName();
medium.getDescription();
medium.getSize();
medium.getType();
medium.getLocation();
medium.getLogicalSize();
medium.getBase().getName();
medium.getBase().getSize();
medium.getBase().getType();
medium.getBase().getLocation();
medium.getBase().getLogicalSize();
}
}
/**
* Cache commonly used Machine properties
*
* @param machine
*/
public static void cacheProperties(IMachine machine) {
synchronized (machine) {
machine.clearCacheNamed("getName", "getState", "getCurrentStateModified", "gotOSTypeId", "getCurrentSnapshot");
machine.getName();
machine.getState();
machine.getCurrentStateModified();
machine.getOSTypeId();
if (machine.getCurrentSnapshot() != null)
machine.getCurrentSnapshot().getName();
}
}
/**
* Launch activity using custom animations. Uses ActivityOptions if on
* JellyBean, otherwise overrides transition
*
* @param parent parent activity
* @param intent intent to launch
*/
public static void startActivity(Activity parent, Intent intent) {
startActivity(parent, intent, R.anim.slide_in_right, R.anim.slide_out_left);
}
/**
* Launch activity using custom animations. Uses ActivityOptions if on
* JellyBean, otherwise overrides transition
*
* @param parent parent activity
* @param intent intent to launch
* @param animIn In animation
* @param animOut Out animation
*/
public static void startActivity(Activity parent, Intent intent, int animIn, int animOut) {
if (isJellyBean())
parent.startActivity(intent, ActivityOptions.makeCustomAnimation(parent, animIn, animOut).toBundle());
else {
parent.startActivity(intent);
parent.overridePendingTransition(animIn, animOut);
}
}
/**
* Launch activity using custom animations. Uses ActivityOptions if on
* JellyBean, otherwise overrides transition
*
* @param parent parent activity
* @param intent intent to launch
*/
public static void startActivityForResult(Activity parent, Intent intent, int requestCode) {
startActivityForResult(parent, intent, requestCode, R.anim.slide_in_right, R.anim.slide_out_left);
}
/**
* Launch activity using custom animations. Uses ActivityOptions if on
* JellyBean, otherwise overrides transition
*
* @param parent parent activity
* @param intent intent to launch
* @param animIn In animation
* @param animOut Out animation
*/
public static void startActivityForResult(Activity parent, Intent intent, int requestCode, int animIn, int animOut) {
if (isJellyBean())
parent.startActivityForResult(intent, requestCode, ActivityOptions.makeCustomAnimation(parent, animIn, animOut).toBundle());
else {
parent.startActivityForResult(intent, requestCode);
parent.overridePendingTransition(animIn, animOut);
}
}
/**
* Override transition for activity closing.
*
* @param activity the activity
*/
public static void overrideBackTransition(Activity activity) {
overrideBackTransition(activity, R.anim.slide_in_left, R.anim.slide_out_right);
}
/**
* Override transition for activity closing.
*
* @param activity the activity
*/
public static void overrideBackTransition(Activity activity, int inAnim, int outAnim) {
activity.overridePendingTransition(inAnim, outAnim);
}
/**
* Scale a bitmap to fit within the desired size
*
* @param bitmap input bitmap
* @param width desired width
* @param height desired height
* @return scaled bitmap which will fit in desired size
*/
public static Bitmap scale(Bitmap bitmap, int width, int height) {
int bWidth = bitmap.getWidth(), bHeight = bitmap.getHeight();
if (bWidth <= width && bHeight <= height)
return bitmap;
if (BuildConfig.DEBUG)
Log.v(TAG, String.format("Scaling bitmap (%1$dx%2$d) --> (%3$dx%4$d)", bWidth, bHeight, width, height));
float wScale = ((float) width) / bWidth;
float hScale = ((float) height) / bHeight;
float scale = Math.min(wScale, hScale);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
if (BuildConfig.DEBUG)
Log.v(TAG, "Scale factor: " + scale);
return Bitmap.createBitmap(bitmap, 0, 0, bWidth, bHeight, matrix, true);
}
public static Point mapPoint(View view, Point point) {
Point mapped = new Point(point.x, point.y);
Matrix matrix = view.getMatrix();
if(!matrix.isIdentity()) {
Matrix inverse = new Matrix();
matrix.invert(inverse);
float []n = { point.x, point.y };
matrix.mapPoints(n);
mapped.x= (int) n[0];
mapped.y= (int) n[1];
}
mapped.offset(-view.getLeft(), -view.getTop());
return mapped;
}
/**
*
* @param view View to search
* @param p point in view's coordinate space
* @return deepest view
*/
public static View getDeepestView(View view, Point p) {
Rect hit = new Rect();
if(view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup)view;
for(int i=0; i<parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
child.getHitRect(hit);
if(hit.contains(p.x, p.y)) {
return getDeepestView(child, mapPoint(child, p));
}
}
}
return view;
}
public static <T> T getDeepestView(View view, Point p, Class<T> clazz) {
View deepest = getDeepestView(view, p);
while(deepest!=null && !(clazz.isAssignableFrom(deepest.getClass()))) {
if(deepest.getParent() instanceof View)
deepest = (View)deepest.getParent();
else
deepest=null;
}
return clazz.cast(deepest);
}
}