org.ttrssreader.utils.Utils.java Source code

Java tutorial

Introduction

Here is the source code for org.ttrssreader.utils.Utils.java

Source

/*
 * Copyright (c) 2015, Nils Braden
 *
 * This file is part of ttrss-reader-fork. 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 3 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, see http://www.gnu.org/licenses/.
 */

package org.ttrssreader.utils;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Vibrator;
import android.support.v4.net.ConnectivityManagerCompat;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Toast;

import org.ttrssreader.R;
import org.ttrssreader.controllers.Controller;
import org.ttrssreader.preferences.Constants;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Set;
import java.util.regex.Pattern;

public class Utils {

    private static final String TAG = Utils.class.getSimpleName();

    public static final long SECOND = 1000;
    public static final long MINUTE = 60 * SECOND;
    public static final long HOUR = 60 * MINUTE;
    public static final long DAY = 24 * HOUR;

    public static final long KB = 1024;
    public static final long MB = KB * KB;

    /**
     * The maximum number of articles to store.
     */
    public static final int ARTICLE_LIMIT = 5000;

    /**
     * Vibrate-Time for vibration when end of list is reached
     */
    public static final long SHORT_VIBRATE = 50;

    /**
     * The time after which data will be fetched again from the server if asked for the data
     */
    public static final long UPDATE_TIME = MINUTE * 30;

    /**
     * The time after which the DB and other data will be cleaned up again,
     */
    public static final long CLEANUP_TIME = DAY;

    /**
     * The Pattern to match image-urls inside HTML img-tags.
     */
    public static final Pattern findImageUrlsPattern = Pattern.compile("<(?:img|video)[^>]+?src=[\"']([^\"']*)",
            Pattern.CASE_INSENSITIVE);

    private static final int ID_RUNNING = 4564561;
    private static final int ID_FINISHED = 7897891;

    /**
     * Different network states
     */
    public static final int NETWORK_NONE = 0;
    public static final int NETWORK_MOBILE = 1;
    public static final int NETWORK_METERED = 2;
    public static final int NETWORK_WIFI = 3;

    /*
     * Check if this is the first run of the app.
     */
    public static boolean checkIsFirstRun() {
        if (Controller.getInstance().isFirstRun()) {
            // Set first run to false anyway.
            Controller.getInstance().setFirstRun(false);
            // Compatibility for already installed apps that don't have this pref yet:
            return Constants.LAST_VERSION_RUN_DEFAULT.equals(Controller.getInstance().getLastVersionRun());
        } else {
            return false;
        }
    }

    /*
     * Check if a new version of the app was installed, returns true if this is the case. This also triggers the reset
     * of the preference noCrashreportsUntilUpdate since with a new update the crash reporting should now be enabled
     * again.
     */
    public static boolean checkIsNewVersion(Context c) {
        String thisVersion = getAppVersionName(c);
        String lastVersionRun = Controller.getInstance().getLastVersionRun();
        Controller.getInstance().setLastVersionRun(thisVersion);

        if (thisVersion.equals(lastVersionRun)) {
            // No new version installed, perhaps a new version exists
            // Only run task once for every session and only if we are online
            if (!checkConnected((ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE)))
                return false;
            if (AsyncTask.Status.PENDING.equals(updateVersionTask.getStatus()))
                updateVersionTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            return false;
        } else {
            // New update was installed, reset noCrashreportsUntilUpdate and return true to display the changelog...
            Controller.getInstance().setNoCrashreportsUntilUpdate(false);
            return true;
        }
    }

    /*
     * Checks the config for a user-defined server, returns true if the config is invalid and the user has not yet
     * entered a valid server adress.
     */
    public static boolean checkIsConfigInvalid() {
        try {
            URI uri = Controller.getInstance().uri();
            if (uri == null || uri.toASCIIString().equals(Constants.URL_DEFAULT + Controller.JSON_END_URL)) {
                return true;
            }
        } catch (URISyntaxException e) {
            return true;
        }
        return false;
    }

    /**
     * Retrieves the packaged version-code of the application
     *
     * @param c - The Activity to retrieve the current version
     * @return the version-string
     */
    public static int getAppVersionCode(Context c) {
        int result;
        try {
            PackageManager manager = c.getPackageManager();
            PackageInfo info = manager.getPackageInfo(c.getPackageName(), 0);
            result = info.versionCode;
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Unable to get application version: " + e.getMessage());
            result = 0;
        }
        return result;
    }

    /**
     * Retrieves the packaged version-name of the application
     *
     * @param c - The Activity to retrieve the current version
     * @return the version-string
     */
    public static String getAppVersionName(Context c) {
        String result;
        try {
            PackageManager manager = c.getPackageManager();
            PackageInfo info = manager.getPackageInfo(c.getPackageName(), 0);
            result = info.versionName;
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Unable to get application version: " + e.getMessage());
            result = "";
        }
        return result;
    }

    /**
     * Checks if the option to work offline is set or if the data-connection isn't established, else returns true. If
     * we are about to connect it waits for maximum one second and then returns the network state without waiting
     * anymore.
     */
    public static boolean isConnected(ConnectivityManager cm) {
        return !Controller.getInstance().workOffline() && checkConnected(cm);
    }

    /**
     * Wrapper for Method checkConnected(ConnectivityManager cm, boolean onlyWifi)
     */
    public static boolean checkConnected(ConnectivityManager cm) {
        return checkConnected(cm, Controller.getInstance().onlyUseWifi(), false);
    }

    /**
     * Only checks the connectivity without regard to the preferences
     */
    public static boolean checkConnected(ConnectivityManager cm, boolean onlyWifi, boolean onlyUnmeteredNetwork) {
        if (cm == null)
            return false;

        NetworkInfo info = cm.getActiveNetworkInfo();
        if (info != null && info.isConnected()) {
            if (onlyWifi && info.getType() != ConnectivityManager.TYPE_WIFI) {
                return false;
            }
            if (onlyUnmeteredNetwork) {
                return !isNetworkMetered(cm);
            }
            return true;
        }
        return false;
    }

    public static int getNetworkType(final ConnectivityManager cm) {
        if (cm == null)
            return NETWORK_NONE;
        final NetworkInfo info = cm.getActiveNetworkInfo();
        if (info == null || !info.isConnected()) {
            return NETWORK_NONE;
        } else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
            return NETWORK_MOBILE;
        } else if (isNetworkMetered(cm)) {
            return NETWORK_METERED;
        } else {
            return NETWORK_WIFI;
        }
    }

    private static boolean isNetworkMetered(ConnectivityManager cm) {
        if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
            return ConnectivityManagerCompat.isActiveNetworkMetered(cm);
        else
            return cm.isActiveNetworkMetered();
    }

    /**
     * Allos to send a toast from a background thread
     *
     * @param context the context, eg. MyApplication.context()
     * @param message like Toast.makeText(...)
     * @param length  like Toast.makeText(...)
     */
    public static void showBackgroundToast(final Context context, final String message, final int length) {
        Handler handler = new Handler(context.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, message, length).show();
            }
        });
    }

    public static void showFinishedNotification(String content, int time, boolean error, Context context) {
        showFinishedNotification(content, time, error, context, new Intent());
    }

    /**
     * Shows a notification with the given parameters
     *
     * @param content the string to display
     * @param time    how long the process took
     * @param error   set to true if an error occured
     * @param context the context
     */
    public static void showFinishedNotification(String content, int time, boolean error, Context context,
            Intent intent) {
        if (context == null)
            return;

        NotificationManager mNotMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        int icon = R.drawable.icon;
        CharSequence title = String.format((String) context.getText(R.string.Utils_DownloadFinishedTitle), time);
        CharSequence ticker = context.getText(R.string.Utils_DownloadFinishedTicker);
        CharSequence text = content;

        if (content == null)
            text = context.getText(R.string.Utils_DownloadFinishedText);

        if (error) {
            icon = R.drawable.icon;
            title = context.getText(R.string.Utils_DownloadErrorTitle);
            ticker = context.getText(R.string.Utils_DownloadErrorTicker);
        }

        Notification notification = buildNotification(context, icon, ticker, title, text, true, intent);
        mNotMan.notify(ID_FINISHED, notification);
    }

    public static void showRunningNotification(Context context, boolean finished) {
        showRunningNotification(context, finished, new Intent());
    }

    /**
     * Shows a notification indicating that something is running. When called with finished=true it removes the
     * notification.
     *
     * @param context  the context
     * @param finished if the notification is to be removed
     */
    private static void showRunningNotification(Context context, boolean finished, Intent intent) {
        if (context == null)
            return;

        NotificationManager mNotMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        // if finished remove notification and return, else display notification
        if (finished) {
            mNotMan.cancel(ID_RUNNING);
            return;
        }

        int icon = R.drawable.notification_icon;
        CharSequence title = context.getText(R.string.Utils_DownloadRunningTitle);
        CharSequence ticker = context.getText(R.string.Utils_DownloadRunningTicker);

        Notification notification = buildNotification(context, icon, ticker, title, "", true, intent);
        mNotMan.notify(ID_RUNNING, notification);
    }

    /**
     * Reads a file from my webserver and parses the content. It containts the version code of the latest supported
     * version. If the version of the installed app is lower then this the feature "Send mail with stacktrace on error"
     * will be disabled to make sure I only receive "new" Bugreports.
     */
    private static AsyncTask<Void, Void, Void> updateVersionTask = new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            // Check last appVersionCheckDate
            long last = Controller.getInstance().appVersionCheckTime();
            if ((System.currentTimeMillis() - last) < (Utils.HOUR * 4))
                return null;

            if (Controller.getInstance().isNoCrashreports())
                return null;

            try {
                URL url = new URL("http://nilsbraden.de/android/tt-rss/minSupportedVersion.txt");
                HttpURLConnection con = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
                con.connect();
                int code = con.getResponseCode();

                if (code < 400 || code >= 600) {

                    BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
                    String content = br.readLine(); // Just read one line!

                    // Only ever read the integer if it matches the regex and is not too long
                    if (content.matches("[0-9]*[\\r\\n]*")) {
                        content = content.replaceAll("[^0-9]*", "");
                        Controller.getInstance().setAppLatestVersion(Integer.parseInt(content));
                    }
                }
            } catch (Exception e) {
                // Empty!
            }

            return null;
        }
    };

    @SuppressWarnings("deprecation")
    public static Notification buildNotification(Context context, int icon, CharSequence ticker, CharSequence title,
            CharSequence text, boolean autoCancel, Intent intent) {
        Notification notification = null;
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        try {
            Notification.Builder builder = new Notification.Builder(context);
            builder.setSmallIcon(icon);
            builder.setTicker(ticker);
            builder.setWhen(System.currentTimeMillis());
            builder.setContentTitle(title);
            builder.setContentText(text);
            builder.setContentIntent(pendingIntent);
            builder.setAutoCancel(autoCancel);
            if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
                notification = builder.getNotification();
            else
                notification = builder.build();
        } catch (Exception re) {
            Log.e(TAG, "Exception while building notification. Does your device propagate the right API-Level? ("
                    + Build.VERSION.SDK_INT + ")", re);
        }

        return notification;
    }

    public static String separateItems(Set<?> att, String separator) {
        if (att == null)
            return "";

        String ret;
        StringBuilder sb = new StringBuilder();
        for (Object s : att) {
            sb.append(s);
            sb.append(separator);
        }
        if (att.size() > 0) {
            ret = sb.substring(0, sb.length() - separator.length());
        } else {
            ret = sb.toString();
        }

        return ret;
    }

    private static final String REGEX_URL = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";

    public static boolean validateURL(String url) {
        return url != null && url.matches(REGEX_URL);

    }

    public static String getTextFromClipboard(Context context) {
        // New Clipboard API
        ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        if (clipboard.hasPrimaryClip()) {

            if (!clipboard.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN))
                return null;

            ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
            CharSequence chars = item.getText();
            if (chars != null && chars.length() > 0) {
                return chars.toString();
            } else {
                Uri pasteUri = item.getUri();
                if (pasteUri != null) {
                    return pasteUri.toString();
                }
            }
        }
        return null;
    }

    public static boolean clipboardHasText(Context context) {
        return (getTextFromClipboard(context) != null);
    }

    public static void alert(Activity activity) {
        alert(activity, false);
    }

    /**
     * Alert the user by a short vibration or a flash of the whole screen.
     */
    public static void alert(Activity activity, boolean error) {
        Vibrator vib = ((Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE));
        if (vib.hasVibrator()) {
            vib.vibrate(Utils.SHORT_VIBRATE);
        } else if (error) {
            // Only flash when user tried to move forward, flashing when reaching the last article looks just wrong.
            Animation flash = AnimationUtils.loadAnimation(activity, R.anim.flash);
            View main = activity.findViewById(R.id.frame_all);
            main.startAnimation(flash);
        }
    }

}