com.vuze.android.remote.AndroidUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.android.remote.AndroidUtils.java

Source

/**
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * <p/>
 * This program 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 2
 * of the License, or (at your option) any later version.
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package com.vuze.android.remote;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.*;
import java.util.regex.Pattern;

import javax.net.ssl.*;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.ByteArrayBuffer;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.*;
import android.app.AlertDialog.Builder;
import android.content.*;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.os.*;
import android.support.annotation.NonNull;
import android.support.v4.app.*;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.CharacterStyle;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.*;
import android.view.WindowManager.BadTokenException;
import android.widget.TextView;
import android.widget.Toast;

import com.vuze.android.remote.activity.MetaSearch;
import com.vuze.android.remote.rpc.RPCException;

/**
 * Some generic Android Utility methods.
 * <p/>
 * Some utility methods specific to this app and requiring Android API.
 * Should, and probably should be in their own class.
 */
@SuppressWarnings("SameParameterValue")
public class AndroidUtils {
    public static final boolean DEBUG = BuildConfig.DEBUG;

    public static final boolean DEBUG_RPC = DEBUG && true;

    public static final boolean DEBUG_MENU = DEBUG && false;

    public static final boolean DEBUG_ADAPTER = DEBUG && false;

    private static final String TAG = "Utils";

    //     . _ - \ /
    private static final Pattern patLineBreakerAround = Pattern.compile("([._\\-\\\\/]+)([^\\s])");

    //   ; ]
    private static final Pattern patLineBreakerAfter = Pattern.compile("([;\\]])([^\\s])");

    private static boolean hasAlertDialogOpen = false;

    private static AlertDialog currentSingleDialog;

    private static Boolean isTV = null;

    private static Boolean hasTouchScreen;

    public static class AlertDialogBuilder {
        public View view;

        public AlertDialog.Builder builder;

        public AlertDialogBuilder(View view, Builder builder) {
            super();
            this.view = view;
            this.builder = builder;
        }
    }

    public static abstract class RunnableWithActivity implements Runnable {
        public Activity activity;
    }

    /**
     * Creates an AlertDialog.Builder that has the proper theme for Gingerbread
     */
    public static AndroidUtils.AlertDialogBuilder createAlertDialogBuilder(Activity activity, int resource) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);

        // Not sure if we need this anymore, but once upon a time, pre-honeycomb
        // (2.x) had dialog color issues
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
            builder.setInverseBackgroundForced(true);
        }

        View view = View.inflate(activity, resource, null);
        builder.setView(view);

        return new AndroidUtils.AlertDialogBuilder(view, builder);
    }

    public static void openSingleAlertDialog(Activity ownerActivity, AlertDialog.Builder builder) {
        openSingleAlertDialog(ownerActivity, builder, null);
    }

    @SuppressWarnings("ConstantConditions")
    public static void openSingleAlertDialog(Activity ownerActivity, AlertDialog.Builder builder,
            final OnDismissListener dismissListener) {
        // We should always be on the UI Thread, so no need to synchronize
        if (hasAlertDialogOpen) {
            if (currentSingleDialog == null || currentSingleDialog.getOwnerActivity() == null
                    || !currentSingleDialog.getOwnerActivity().isFinishing()) {
                if (DEBUG) {
                    Log.e(TAG, "Already have Alert Dialog Open " + currentSingleDialog);
                }
                return;
            }
        }

        if (DEBUG && hasAlertDialogOpen) {
            Log.e(TAG, "hasAlertDialogOpen flag ON, but dialog isn't showing");
        }

        hasAlertDialogOpen = true;

        try {
            currentSingleDialog = builder.show();
            currentSingleDialog.setOwnerActivity(ownerActivity);
            if (DEBUG) {
                Log.d(TAG, "Alert Dialog Open " + getCompressedStackTrace());
            }

            // Note: There's a builder.setOnDismissListener(), but it's API 17
            currentSingleDialog.setOnDismissListener(new OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    hasAlertDialogOpen = false;
                    if (dismissListener != null) {
                        dismissListener.onDismiss(dialog);
                    }
                }
            });
        } catch (BadTokenException bte) {
            // android.view.WindowManager$BadTokenException: Unable to add window --
            // token android.os.BinderProxy@42043ff8 is not valid; is your activity
            // running?
            // ignore.  We checked activity.isFinishing() earlier.. not much we
            // can do
            Log.e(TAG, "AlertDialog", bte);
        }
    }

    public static void showConnectionError(Activity activity, Throwable t, boolean allowContinue) {
        if (AndroidUtils.DEBUG) {
            Log.d(TAG, "showConnectionError " + AndroidUtils.getCompressedStackTrace(t, 0, 9));
        }

        Throwable t2 = (t instanceof RPCException) ? t.getCause() : t;

        if ((t2 instanceof HttpHostConnectException) || (t2 instanceof UnknownHostException)) {
            String message = t.getMessage();
            if (AndroidUtils.DEBUG) {
                Log.d(TAG, "showConnectionError Yup " + message);
            }
            if (message != null && message.contains("pair.vuze.com")) {
                showConnectionError(activity, R.string.connerror_pairing, allowContinue);
                return;
            }
        }
        String message = "";
        while (t != null) {
            String newMessage = t.getMessage();
            if (newMessage != null && message.contains(newMessage)) {
                t = t.getCause();
                continue;
            }
            message += newMessage + "\n";
            Throwable tReplace = t;
            while (tReplace != null) {
                Class<?> cla = tReplace.getClass();
                String name = cla.getName();
                message = message.replaceAll(name + ": ", cla.getSimpleName() + ": ");
                tReplace = tReplace.getCause();
            }
            t = t.getCause();
        }
        showConnectionError(activity, message, allowContinue);
    }

    public static void showConnectionError(Activity activity, int errMsgID, boolean allowContinue) {
        String errMsg = activity.getResources().getString(errMsgID);
        showConnectionError(activity, errMsg, allowContinue);
    }

    public static void showConnectionError(final Activity activity, final String errMsg,
            final boolean allowContinue) {
        if (AndroidUtils.DEBUG) {
            Log.d(TAG, "showConnectionError.string " + AndroidUtils.getCompressedStackTrace());
        }
        if (activity == null) {
            Log.e(null, "No activity for error message " + errMsg);
            return;
        }
        activity.runOnUiThread(new Runnable() {
            public void run() {
                if (activity.isFinishing()) {
                    if (DEBUG) {
                        System.out.println("can't display -- finishing");
                    }
                    return;
                }
                Builder builder = new AlertDialog.Builder(activity).setTitle(R.string.error_connecting)
                        .setMessage(errMsg).setCancelable(true)
                        .setNegativeButton(R.string.action_logout, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                if (activity.isTaskRoot()) {
                                    RemoteUtils.openRemoteList(activity);
                                }
                                activity.finish();
                            }
                        });
                if (allowContinue) {
                    builder.setPositiveButton(R.string.button_continue, new OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    });
                }
                AndroidUtils.openSingleAlertDialog(activity, builder);
            }
        });

    }

    public static void showDialog(Activity activity, int titleID, CharSequence msg) {
        String title = activity.getResources().getString(titleID);
        showDialog(activity, title, msg);
    }

    public static void showDialog(final Activity activity, final CharSequence title, final CharSequence msg) {
        activity.runOnUiThread(new Runnable() {
            public void run() {
                if (activity.isFinishing()) {
                    if (DEBUG) {
                        System.out.println("can't display -- finishing");
                    }
                    return;
                }
                Builder builder = new AlertDialog.Builder(activity).setMessage(msg).setCancelable(true)
                        .setNegativeButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        });
                if (title != null) {
                    builder.setTitle(title);
                }
                builder.show();
            }
        });

    }

    public static void showFeatureRequiresVuze(final Activity activity, final String feature) {
        activity.runOnUiThread(new Runnable() {
            public void run() {
                if (activity.isFinishing()) {
                    if (DEBUG) {
                        System.out.println("can't display -- finishing");
                    }
                    return;
                }
                String msg = activity.getResources().getString(R.string.vuze_required, feature);
                Builder builder = new AlertDialog.Builder(activity).setMessage(msg).setCancelable(true)
                        .setPositiveButton(android.R.string.ok, new OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        });
                builder.show();
            }
        });

    }

    // ACTION_POWER_CONNECTED
    public static boolean isPowerConnected(Context context) {
        Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        if (intent == null) {
            return true;
        }
        int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB
                || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
    }

    // From http://
    public static void openFileChooser(Activity activity, String mimeType, int requestCode) {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType(mimeType);
        intent.addCategory(Intent.CATEGORY_OPENABLE);

        // special intent for Samsung file manager
        Intent sIntent = new Intent("com.sec.android.app.myfiles.PICK_DATA");

        sIntent.putExtra("CONTENT_TYPE", mimeType);
        sIntent.addCategory(Intent.CATEGORY_DEFAULT);

        Intent chooserIntent;
        if (activity.getPackageManager().resolveActivity(sIntent, 0) != null) {
            chooserIntent = Intent.createChooser(sIntent, "Open file");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { intent });
        } else {
            chooserIntent = Intent.createChooser(intent, "Open file");
        }

        if (chooserIntent != null) {
            try {
                activity.startActivityForResult(chooserIntent, requestCode);
                return;
            } catch (android.content.ActivityNotFoundException ex) {
            }
        }
        Toast.makeText(activity.getApplicationContext(),
                activity.getResources().getString(R.string.no_file_chooser), Toast.LENGTH_SHORT).show();
    }

    public static void handleConsoleMessageFroyo(Context ctx, String message, String sourceId, int lineNumber,
            String page) {
        Log.d("console.log", message + " -- line " + lineNumber + " of " + sourceId);
        if (message != null && message.startsWith("Uncaught")) {
            if (sourceId == null) {
                sourceId = "unknown";
            }
            if (sourceId.indexOf('/') > 0) {
                sourceId = sourceId.substring(sourceId.lastIndexOf('/'));
            }
            int qPos = sourceId.indexOf('?');
            if (qPos > 0 && sourceId.length() > 1) {
                sourceId = sourceId.substring(0, qPos);
            }
            String s = sourceId + ":" + lineNumber + " " + message.substring(9);
            if (s.length() > 100) {
                s = s.substring(0, 100);
            }
            VuzeEasyTracker.getInstance(ctx).logError(s, page);
        }
    }

    public static void linkify(View view, int widgetId) {
        TextView textview = (TextView) view.findViewById(widgetId);
        if (textview != null) {
            textview.setMovementMethod(LinkMovementMethod.getInstance());
        } else {
            Log.d(TAG, "NO " + widgetId);
        }
    }

    /**
     * Remove all extras from intent
     */
    public static void clearExtras(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras == null) {
            return;
        }
        for (String key : extras.keySet()) {
            intent.removeExtra(key);
        }
    }

    /**
     * Android doesn't fade out disabled menu item icons, so do it ourselves
     */
    public static void fixupMenuAlpha(Menu menu) {
        for (int i = 0; i < menu.size(); i++) {
            MenuItem item = menu.getItem(i);
            Drawable icon = item.getIcon();
            if (icon != null) {
                icon.setAlpha(item.isEnabled() ? 255 : 64);
            }
        }
    }

    public static class ValueStringArray {
        public int size;

        public long values[];

        public String strings[];

        public ValueStringArray(long[] value, String[] string) {
            this.values = value;
            this.strings = string;
            this.size = Math.min(values.length, string.length);
        }

    }

    public static ValueStringArray getValueStringArray(Resources resources, int id) {
        String[] stringArray = resources.getStringArray(id);
        String[] strings = new String[stringArray.length];
        long[] values = new long[stringArray.length];

        for (int i = 0; i < stringArray.length; i++) {
            String[] s = stringArray[i].split(",");
            values[i] = Integer.parseInt(s[0]);
            strings[i] = s[1];
        }
        return new ValueStringArray(values, strings);
    }

    public static boolean executeSearch(String search, Context context) {
        return executeSearch(search, context, null);
    }

    public static boolean executeSearch(String search, Context context, SessionInfo sessionInfo) {
        Intent myIntent = new Intent(Intent.ACTION_SEARCH);
        myIntent.setClass(context, MetaSearch.class);
        RemoteProfile remoteProfile = sessionInfo.getRemoteProfile();
        if (remoteProfile != null && remoteProfile.getRemoteType() == RemoteProfile.TYPE_LOOKUP) {
            Bundle bundle = new Bundle();
            bundle.putString("com.vuze.android.remote.searchsource", sessionInfo.getRpcRoot());
            if (remoteProfile.getRemoteType() == RemoteProfile.TYPE_LOOKUP) {
                bundle.putString("com.vuze.android.remote.ac", remoteProfile.getAC());
            }
            bundle.putString(SessionInfoManager.BUNDLE_KEY, remoteProfile.getID());

            myIntent.putExtra(SearchManager.APP_DATA, bundle);
        }

        myIntent.putExtra(SearchManager.QUERY, search);

        context.startActivity(myIntent);
        return true;
    }

    public static boolean isURLAlive(String URLName) {
        if (isURLAlive(URLName, 1000, 1000)) {
            return true;
        }
        if (isURLAlive(URLName, 10000, 5000)) {
            return true;
        }
        return false;
    }

    private static boolean isURLAlive(String URLName, int conTimeout, int readTimeout) {
        try {
            HttpURLConnection.setFollowRedirects(false);

            URL url = new URL(URLName);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            if (con instanceof HttpsURLConnection) {
                HttpsURLConnection conHttps = (HttpsURLConnection) con;

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                    SSLContext ctx = SSLContext.getInstance("TLS");
                    ctx.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() },
                            new SecureRandom());
                    conHttps.setSSLSocketFactory(ctx.getSocketFactory());
                }

                conHttps.setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
            }

            con.setConnectTimeout(conTimeout);
            con.setReadTimeout(readTimeout);
            con.setRequestMethod("HEAD");
            con.getResponseCode();
            if (DEBUG) {
                Log.d(TAG, "isLive? conn result=" + con.getResponseCode() + ";" + con.getResponseMessage());
            }
            return true;
        } catch (Exception e) {
            if (DEBUG) {
                Log.e(TAG, "isLive " + URLName, e);
            }
            return false;
        }
    }

    public static boolean readInputStreamIfStartWith(InputStream is, ByteArrayBuffer bab, byte[] startsWith)
            throws IOException {

        byte[] buffer = new byte[32 * 1024];

        boolean first = true;

        try {
            while (true) {

                int len = is.read(buffer);

                if (len <= 0) {

                    break;
                }

                bab.append(buffer, 0, len);

                if (first) {
                    first = false;
                    for (int i = 0; i < startsWith.length; i++) {
                        if (startsWith[i] != buffer[i]) {
                            return false;
                        }
                    }
                }
            }

            return bab.isEmpty() ? false : true;

        } finally {

            is.close();
        }
    }

    public static byte[] readInputStreamAsByteArray(InputStream is) throws IOException {
        int available = is.available();
        if (available <= 0) {
            available = 32 * 1024;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream(available);

        byte[] buffer = new byte[32 * 1024];

        try {
            while (true) {

                int len = is.read(buffer);

                if (len <= 0) {

                    break;
                }

                baos.write(buffer, 0, len);
            }

            return (baos.toByteArray());

        } finally {

            is.close();
        }
    }

    public static void setSpanBetweenTokens(SpannableString ss, String text, String token, CharacterStyle... cs) {
        // Start and end refer to the points where the span will apply
        int tokenLen = token.length();
        int base = 0;

        while (true) {
            int start = text.indexOf(token, base);
            int end = text.indexOf(token, start + tokenLen);

            if (start < 0 || end < 0) {
                break;
            }

            base = end + tokenLen;

            for (CharacterStyle c : cs) {
                ss.setSpan(CharacterStyle.wrap(c), start + tokenLen, end, 0);
            }

            Drawable blankDrawable = new Drawable() {

                @Override
                public void setColorFilter(ColorFilter cf) {
                }

                @Override
                public void setAlpha(int alpha) {
                }

                @Override
                public int getOpacity() {
                    return 0;
                }

                @Override
                public void draw(Canvas canvas) {
                }
            };

            // because AbsoluteSizeSpan(0) doesn't work on older versions
            ss.setSpan(new ImageSpan(blankDrawable), start, start + tokenLen, 0);
            ss.setSpan(new ImageSpan(blankDrawable), end, end + tokenLen, 0);
        }
    }

    public static void invalidateOptionsMenuHC(final Activity activity) {
        invalidateOptionsMenuHC(activity, (android.support.v7.view.ActionMode) null);
    }

    public static void invalidateOptionsMenuHC(final Activity activity, final ActionMode mActionMode) {
        if (activity == null) {
            return;
        }
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (activity instanceof FragmentActivity) {
                    FragmentActivity aba = (FragmentActivity) activity;
                    aba.supportInvalidateOptionsMenu();
                } else {
                    ActivityCompat.invalidateOptionsMenu(activity);
                }
                invalidateActionMode();
            }

            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            private void invalidateActionMode() {
                if (mActionMode != null) {
                    mActionMode.invalidate();
                }
            }
        });
    }

    public static void invalidateOptionsMenuHC(final Activity activity,
            final android.support.v7.view.ActionMode mActionMode) {
        if (activity == null) {
            return;
        }
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (activity.isFinishing()) {
                    return;
                }
                if (mActionMode != null) {
                    mActionMode.invalidate();
                    return;
                }
                if (activity instanceof FragmentActivity) {
                    FragmentActivity aba = (FragmentActivity) activity;
                    aba.supportInvalidateOptionsMenu();
                } else {
                    ActivityCompat.invalidateOptionsMenu(activity);
                }
            }
        });
    }

    // From FileUtil.java
    public static void copyFile(final InputStream _source, final File _dest, boolean _close_input_stream)

            throws IOException {
        FileOutputStream dest = null;

        try {
            // FileNotFoundException
            dest = new FileOutputStream(_dest);

            copyFile(_source, dest, _close_input_stream);

        } finally {

            try {
                if (_close_input_stream) {

                    _source.close();
                }
            } catch (IOException e) {
            }

            if (dest != null) {

                dest.close();
            }
        }
    }

    // From FileUtil.java
    public static void copyFile(InputStream is, OutputStream os, boolean closeInputStream)

            throws IOException {
        try {

            if (!(is instanceof BufferedInputStream)) {

                is = new BufferedInputStream(is, 8192);
            }

            byte[] buffer = new byte[65536 * 2];

            while (true) {

                int len = is.read(buffer);

                if (len == -1) {

                    break;
                }

                os.write(buffer, 0, len);
            }
        } finally {
            try {
                if (closeInputStream) {
                    is.close();
                }
            } catch (IOException e) {

            }

            os.close();
        }
    }

    public static boolean readURL(String uri, ByteArrayBuffer bab, byte[] startsWith)
            throws IllegalArgumentException {

        BasicHttpParams basicHttpParams = new BasicHttpParams();
        HttpProtocolParams.setUserAgent(basicHttpParams, "Vuze Android Remote");
        DefaultHttpClient httpclient = new DefaultHttpClient(basicHttpParams);

        // Prepare a request object
        HttpRequestBase httpRequest = new HttpGet(uri);

        // Execute the request
        HttpResponse response;

        try {
            response = httpclient.execute(httpRequest);

            HttpEntity entity = response.getEntity();
            if (entity != null) {

                // A Simple JSON Response Read
                InputStream is = entity.getContent();
                return readInputStreamIfStartWith(is, bab, startsWith);
            }

        } catch (Exception e) {
            VuzeEasyTracker.getInstance().logError(e);
        }

        return false;
    }

    public static void copyUrlToFile(String uri, File outFile) throws ClientProtocolException, IOException {

        BasicHttpParams basicHttpParams = new BasicHttpParams();
        HttpProtocolParams.setUserAgent(basicHttpParams, "Vuze Android Remote");
        DefaultHttpClient httpclient = new DefaultHttpClient(basicHttpParams);

        // Prepare a request object
        HttpRequestBase httpRequest = new HttpGet(uri);

        // Execute the request
        HttpResponse response;

        response = httpclient.execute(httpRequest); // HttpHostConnectException

        HttpEntity entity = response.getEntity();
        if (entity != null) {

            // A Simple JSON Response Read
            InputStream is = entity.getContent();
            copyFile(is, outFile, true); // FileNotFoundException
        }
    }

    public static File getDownloadDir() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            return getDownloadDir_Froyo();
        }
        return new File(Environment.getExternalStorageDirectory() + "/downloads");
    }

    @TargetApi(Build.VERSION_CODES.FROYO)
    private static File getDownloadDir_Froyo() {
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
    }

    /**
     * Same as {@link Activity#runOnUiThread(Runnable)}, except ensures
     * activity still exists while in UI Thread, before executing runnable
     */
    public static void runOnUIThread(final android.support.v4.app.Fragment fragment, final Runnable runnable) {
        Activity activity = fragment.getActivity();
        if (activity == null) {
            return;
        }
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Activity activity = fragment.getActivity();
                if (activity == null) {
                    return;
                }
                if (runnable instanceof RunnableWithActivity) {
                    ((RunnableWithActivity) runnable).activity = activity;
                }
                runnable.run();
            }
        });
    }

    /**
     * Better line breaking for text view.  Puts invisible whitespace around<br>
     * . _ - \ /
     * <br>
     * and after<br>
     * ] ;
     * <br>
     */
    public static String lineBreaker(String s) {
        s = patLineBreakerAfter.matcher(s).replaceAll("$1\u200B$2");
        s = patLineBreakerAround.matcher(s).replaceAll("\u200B$1\u200B$2");
        return s;
    }

    public static String getCompressedStackTrace() {
        try {
            throw new Exception();
        } catch (Exception e) {
            return getCompressedStackTrace(e, 1, 12);
        }
    }

    public static String getCompressedStackTrace(int limit) {
        try {
            throw new Exception();
        } catch (Exception e) {
            return getCompressedStackTrace(e, 1, limit);
        }
    }

    public static String getCompressedStackTrace(Throwable t, int startAt, int limit) {
        try {
            StackTraceElement[] stackTrace = t.getStackTrace();
            if (stackTrace.length < startAt) {
                return "";
            }
            StringBuilder sb = new StringBuilder("");
            for (int i = startAt; i < stackTrace.length && i < startAt + limit; i++) {
                StackTraceElement element = stackTrace[i];
                String classname = element.getClassName();
                String cnShort;
                boolean showLineNumber = true;
                boolean breakAfter = false;
                if (classname.startsWith("com.vuze.android.remote.")) {
                    cnShort = classname.substring(24, classname.length());
                } else if (classname.equals("android.os.Handler")) {
                    showLineNumber = false;
                    cnShort = "Handler";
                } else if (classname.equals("android.os.Looper")) {
                    showLineNumber = false;
                    cnShort = "Looper";
                    breakAfter = true;
                } else if (classname.length() < 9) { // include full if something like aa.ab.ac
                    cnShort = classname;
                } else {
                    int len = classname.length();
                    int start = len > 14 ? len - 14 : 0;

                    int pos = classname.indexOf('.', start);
                    if (pos >= 0) {
                        start = pos + 1;
                    }
                    cnShort = classname.substring(start, len);
                }
                if (i != startAt) {
                    sb.append(", ");
                }
                sb.append(cnShort);
                sb.append('.');
                sb.append(element.getMethodName());
                if (showLineNumber) {
                    sb.append(':');
                    sb.append(element.getLineNumber());
                }
                if (breakAfter) {
                    break;
                }
            }
            Throwable cause = t.getCause();
            if (cause != null) {
                sb.append("\n|Cause ");
                sb.append(cause.getClass().getSimpleName());
                if (cause instanceof Resources.NotFoundException || cause instanceof RuntimeException) {
                    sb.append(' ');
                    sb.append(cause.getMessage());
                }
                sb.append(' ');
                sb.append(getCompressedStackTrace(cause, 0, 9));
            }
            return sb.toString();
        } catch (Throwable derp) {
            return "derp " + derp.getClass().getSimpleName();
        }
    }

    public static String getCauses(Throwable e) {
        try {
            StringBuilder sb = new StringBuilder();
            while (e != null) {
                if (sb.length() > 0) {
                    sb.append(", ");
                }
                sb.append(e.getClass().getSimpleName());
                e = e.getCause();
            }

            return sb.toString();

        } catch (Throwable derp) {
            return "derp " + derp.getClass().getSimpleName();
        }
    }

    public static ComponentInfo getComponentInfo(ResolveInfo info) {
        if (info.activityInfo != null)
            return info.activityInfo;
        if (info.serviceInfo != null)
            return info.serviceInfo;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return getComponentInfo_v19(info);
        }
        return null;
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static ComponentInfo getComponentInfo_v19(ResolveInfo info) {
        if (info.providerInfo != null)
            return info.providerInfo;
        return null;
    }

    public static String getStatesString(int[] ints) {
        String[] s = new String[ints.length];
        for (int i = 0; i < ints.length; i++) {
            int state = ints[i];
            switch (state) {
            case android.R.attr.state_above_anchor:
                s[i] = "state_above_anchor";
                break;

            case android.R.attr.state_accelerated:
                s[i] = "state_accelerated";
                break;

            case android.R.attr.state_activated:
                s[i] = "state_activated";
                break;

            case android.R.attr.state_active:
                s[i] = "state_active";
                break;

            case android.R.attr.state_checkable:
                s[i] = "state_checkable";
                break;

            case android.R.attr.state_checked:
                s[i] = "state_checked";
                break;

            case android.R.attr.state_drag_can_accept:
                s[i] = "state_drag_can_accept";
                break;

            case android.R.attr.state_drag_hovered:
                s[i] = "state_drag_hovered";
                break;

            case android.R.attr.state_empty:
                s[i] = "state_empty";
                break;

            case android.R.attr.state_enabled:
                s[i] = "state_enabled";
                break;

            case android.R.attr.state_expanded:
                s[i] = "state_expanded";
                break;

            case android.R.attr.state_focused:
                s[i] = "state_focused";
                break;

            case android.R.attr.state_hovered:
                s[i] = "state_hovered";
                break;

            case android.R.attr.state_last:
                s[i] = "state_last";
                break;

            case android.R.attr.state_long_pressable:
                s[i] = "state_long_pressable";
                break;

            case android.R.attr.state_middle:
                s[i] = "state_middle";
                break;

            case android.R.attr.state_multiline:
                s[i] = "state_multiline";
                break;

            case android.R.attr.state_pressed:
                s[i] = "state_pressed";
                break;

            case android.R.attr.state_selected:
                s[i] = "state_selected";
                break;

            case android.R.attr.state_single:
                s[i] = "state_single";
                break;

            case android.R.attr.state_window_focused:
                s[i] = "state_window_focused";
                break;
            default:
                s[i] = "" + state;
            }
        }
        return Arrays.toString(s);
    }

    public static int indexOfAny(String findIn, String findAnyChar, int startPos) {
        for (int i = 0; i < findAnyChar.length(); i++) {
            char c = findAnyChar.charAt(i);
            int pos = findIn.indexOf(c, startPos);
            if (pos >= 0) {
                return pos;
            }
        }
        return -1;
    }

    public static int lastindexOfAny(String findIn, String findAnyChar, int startPos) {
        if (startPos > findIn.length()) {
            return -1;
        }
        for (int i = 0; i < findAnyChar.length(); i++) {
            char c = findAnyChar.charAt(i);
            int pos = startPos >= 0 ? findIn.lastIndexOf(c, startPos) : findIn.lastIndexOf(c);
            if (pos >= 0) {
                return pos;
            }
        }
        return -1;
    }

    public static boolean showDialog(DialogFragment dlg, FragmentManager fm, String tag) {
        try {
            dlg.show(fm, tag);
            return true;
        } catch (IllegalStateException e) {
            // Activity is no longer active (ie. most likely paused)
            return false;
        }
    }

    public static boolean isAmazonFire() {
        return Build.MODEL.startsWith("AFT");
    }

    public static boolean isTV() {
        if (isTV == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
                Context context = VuzeRemoteApp.getContext();
                UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
                isTV = uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
                if (!isTV) {
                    // alternate check
                    isTV = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)
                            || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                            || context.getPackageManager().hasSystemFeature("android.software.leanback_only");
                    if (isTV && DEBUG) {
                        Log.d(TAG, "isTV: not UI_MODE_TYPE_TELEVISION, however is has system "
                                + "feature suggesting tv");
                    }

                    if (!isTV) {
                        String[] names = context.getPackageManager().getSystemSharedLibraryNames();
                        for (String name : names) {
                            if (name.startsWith("com.google.android.tv")) {
                                isTV = true;
                                if (DEBUG) {
                                    Log.d(TAG, "isTV: found tv shared library. Assuming tv");
                                }
                                break;
                            }
                        }
                    }
                }
            } else {
                isTV = false;
            }
        }

        return isTV;
    }

    public static boolean hasTouchScreen() {
        if (hasTouchScreen == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
                hasTouchScreen = VuzeRemoteApp.getContext().getPackageManager()
                        .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
            } else {
                hasTouchScreen = true;
            }
        }
        return hasTouchScreen;
    }

    public static boolean usesNavigationControl() {
        Configuration configuration = VuzeRemoteApp.getContext().getResources().getConfiguration();
        if (configuration.navigation == Configuration.NAVIGATION_NONAV) {
            return false;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_FINGER) {
            return false;
        } else if (configuration.navigation == Configuration.NAVIGATION_DPAD) {
            return true;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
            return true;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_UNDEFINED) {
            return true;
        } else if (configuration.navigationHidden == Configuration.NAVIGATIONHIDDEN_YES) {
            return true;
        } else if (configuration.uiMode == Configuration.UI_MODE_TYPE_TELEVISION) {
            return true;
        }
        return false;
    }

    public static int[] removeState(int[] states, int state) {
        for (int i = 0; i < states.length; i++) {
            if (states[i] == state) {
                int[] newState = new int[states.length - 1];
                if (i > 0) {
                    System.arraycopy(states, 0, newState, 0, i);
                }
                System.arraycopy(states, i + 1, newState, i, states.length - i - 1);
                return newState;
            }
        }
        return states;
    }

    public static int[] addState(int[] states, int state) {
        for (int oldState : states) {
            if (oldState == state) {
                return states;
            }
        }
        int[] newState = new int[states.length + 1];
        System.arraycopy(states, 0, newState, 1, states.length);
        newState[0] = state;
        return newState;
    }

    @SuppressLint("InlinedApi")
    public static String statesDebug(int[] states) {
        if (states == null) {
            return "null";
        }
        if (states.length == 0) {
            return "[]";
        }
        Map<Integer, String> map = new HashMap<>();
        map.put(android.R.attr.state_above_anchor, "above_anchor");
        map.put(android.R.attr.state_accelerated, "accelerated");
        map.put(android.R.attr.state_activated, "activated");
        map.put(android.R.attr.state_active, "active");
        map.put(android.R.attr.state_checkable, "checkable");
        map.put(android.R.attr.state_checked, "checked");
        map.put(android.R.attr.state_drag_can_accept, "drag_can_accept");
        map.put(android.R.attr.state_drag_hovered, "drag_hovered");
        map.put(android.R.attr.state_empty, "empty");
        map.put(android.R.attr.state_enabled, "enabled");
        map.put(android.R.attr.state_expanded, "expanded");
        map.put(android.R.attr.state_first, "first");
        map.put(android.R.attr.state_focused, "focused");
        map.put(android.R.attr.state_hovered, "hovered");
        map.put(android.R.attr.state_last, "last");
        map.put(android.R.attr.state_long_pressable, "long_pressable");
        map.put(android.R.attr.state_middle, "middle");
        map.put(android.R.attr.state_multiline, "multiline");
        map.put(android.R.attr.state_pressed, "pressed");
        map.put(android.R.attr.state_selected, "selected");
        map.put(android.R.attr.state_single, "single");
        map.put(android.R.attr.state_window_focused, "window_focused");

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int state : states) {
            if (sb.length() > 1) {
                sb.append(',');
            }
            String s = map.get(state);
            if (s == null) {
                sb.append(state);
            } else {
                sb.append(s);
            }
        }
        sb.append(']');

        return sb.toString();
    }

    public static int integerCompare(int lhs, int rhs) {
        return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
    }

    public static int longCompare(long lhs, long rhs) {
        return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
    }

    public static boolean hasPermisssion(@NonNull Context context, @NonNull String permission) {
        PackageManager packageManager = context.getPackageManager();
        try {
            packageManager.getPermissionInfo(permission, 0);
        } catch (PackageManager.NameNotFoundException e) {
            Log.d("Perms", "requestPermissions: Permission " + permission + " doesn't exist.  Assuming granted.");
            return true;
        }
        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
    }

    public static String getProcessName(Context context, int pID) {
        BufferedReader cmdlineReader = null;
        try {
            cmdlineReader = new BufferedReader(
                    new InputStreamReader(new FileInputStream("/proc/" + pID + "/cmdline"), "iso-8859-1"), 100);
            int c;
            StringBuilder processName = new StringBuilder();
            while ((c = cmdlineReader.read()) > 0) {
                processName.append((char) c);
            }
            return processName.toString();
        } catch (Throwable ignore) {
        } finally {
            if (cmdlineReader != null) {
                try {
                    cmdlineReader.close();
                } catch (IOException e) {
                }
            }
        }
        return getProcessName_PM(context, pID);
    }

    /**
     * Get Process Name by getRunningAppProcesses
     * <p/>
     * It's been reported that sometimes, the list returned from
     * getRunningAppProcesses simply doesn't contain your own process
     * (especially when called from Application).
     * Use {@link #getProcessName(Context, int)} instead
     */
    public static String getProcessName_PM(Context context, int pID) {
        String processName = "";
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List l = am.getRunningAppProcesses();
        Iterator i = l.iterator();
        PackageManager pm = context.getPackageManager();
        while (i.hasNext()) {
            ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
            try {
                if (info.pid == pID) {
                    return info.processName;
                }
            } catch (Exception e) {
                Log.e(TAG, "getAppName: error", e);
            }
        }
        return processName;
    }

    public static Thread getThreadByName(String name) {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();

        while (tg.getParent() != null) {

            tg = tg.getParent();
        }

        Thread[] threads = new Thread[tg.activeCount() + 1024];

        tg.enumerate(threads, true);

        boolean bad_found = false;

        for (int i = 0; i < threads.length; i++) {

            Thread t = threads[i];

            if (t != null && t.isAlive() && t != Thread.currentThread() && !t.isDaemon()
                    && t.getName().equals(name)) {
                return t;
            }
        }

        return null;
    }

    public static void dumpBatteryStats(Context context) {
        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = context.registerReceiver(null, ifilter);

        Bundle bundle = batteryStatus.getExtras();
        for (String key : bundle.keySet()) {
            Object value = bundle.get(key);
            Log.d(TAG, String.format("Battery,%s=%s (%s)", key, value.toString(), value.getClass().getName()));
        }
    }
}