cz.maresmar.sfm.app.SfmApp.java Source code

Java tutorial

Introduction

Here is the source code for cz.maresmar.sfm.app.SfmApp.java

Source

/*
 * SmartFoodMenu - Android application for canteens extendable with plugins
 *
 * Copyright  2016-2018  Martin Mare <mmrmartin[at]gmail[dot]com>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

package cz.maresmar.sfm.app;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.text.InputType;
import android.util.Log;
import android.widget.EditText;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.security.ProviderInstaller;
import com.mikepenz.aboutlibraries.Libs;
import com.mikepenz.aboutlibraries.LibsBuilder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import cz.maresmar.sfm.Assert;
import cz.maresmar.sfm.BuildConfig;
import cz.maresmar.sfm.R;
import timber.log.Timber;

/**
 * Application singleton that is used for logging using Timber
 * @see Application
 */
public class SfmApp extends Application implements ProviderInstaller.ProviderInstallListener {

    File mTmpFile;
    PrintStream mPrintStream;

    /**
     * Shows about activity
     * @param context Some valid context
     */
    public static void startAboutActivity(@NonNull Context context) {
        new LibsBuilder().withActivityTitle(context.getString(R.string.drawer_about))
                //provide a style (optional) (LIGHT, DARK, LIGHT_DARK_TOOLBAR)
                .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
                .withAboutAppName(context.getString(R.string.app_name)).withLicenseShown(true)
                .withLicenseDialog(true).withVersionShown(false)
                //start the activity
                .start(context);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // Register logging method
        if (BuildConfig.DEBUG) {
            // Let's log to builtin Android logger
            Timber.plant(new Timber.DebugTree());
        }

        try {
            File logsDir = new File(getCacheDir(), "logs");
            if (!logsDir.exists()) {
                boolean result = logsDir.mkdir();

                if (BuildConfig.DEBUG) {
                    Assert.that(result, "Logs folder wasn't created");
                }
            }
            mTmpFile = File.createTempFile("sfm", ".log", logsDir);
            mPrintStream = new PrintStream(new FileOutputStream(mTmpFile, true));

            Timber.plant(new ReleaseTree(mPrintStream));
        } catch (IOException e) {
            e.printStackTrace();
        }

        Timber.i("Starting sfm %s", getString(R.string.app_version));

        NotificationContract.initNotificationChannels(this);

        // Update the security provider
        ProviderInstaller.installIfNeededAsync(this, this);
    }

    @Override
    public void onTerminate() {
        mPrintStream.close();
        boolean successful = mTmpFile.delete();

        if (BuildConfig.DEBUG) {
            Assert.that(successful, "Deleting log file failed");
        }

        super.onTerminate();
    }

    /**
     * Returns the file where the logs are written
     * @return The log file
     */
    @NonNull
    public File getLogFile() {
        mPrintStream.flush();
        return mTmpFile;
    }

    /**
     * Opens email app with log file
     */
    public void sendFeedback(Context context) {
        // Shows subjects dialog
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(R.string.feedback_subject_title);
        builder.setMessage(R.string.feedback_subject_msg);

        // Set up the input
        final EditText input = new EditText(context);
        // Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
        input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE
                | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
        builder.setView(input);

        // Set up the buttons
        builder.setPositiveButton(android.R.string.ok,
                (dialog, which) -> sendFeedback(context, input.getText().toString()));
        builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel());

        builder.show();
    }

    private void sendFeedback(Context context, String subject) {
        Timber.i("Device %s (%s) on SDK %d", Build.DEVICE, Build.MANUFACTURER, Build.VERSION.SDK_INT);

        File logFile = getLogFile();
        Uri logUri = FileProvider.getUriForFile(this, "cz.maresmar.sfm.FileProvider", logFile);

        Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
        emailIntent.setData(Uri.parse("mailto:"));
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { "mmrmartin+dev" + '@' + "gmail.com" });
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "[sfm] " + subject);
        emailIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.feedback_mail_text));
        emailIntent.putExtra(Intent.EXTRA_STREAM, logUri);
        emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(emailIntent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, logUri,
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }

        context.startActivity(
                Intent.createChooser(emailIntent, getString(R.string.feedback_choose_email_app_dialog)));
    }

    /**
     * This method is only called if the provider is successfully updated
     * (or is already up-to-date).
     */
    @Override
    public void onProviderInstalled() {
        Timber.i("Security provider is up-to-date");
    }

    /**
     * This method is called if updating fails; the error code indicates
     * whether the error is recoverable.
     */
    @Override
    public void onProviderInstallFailed(int errorCode, Intent intent) {
        GoogleApiAvailability availability = GoogleApiAvailability.getInstance();
        if (availability.isUserResolvableError(errorCode)) {
            // Recoverable error. Show a dialog prompting the user to
            // install/update/enable Google Play services.
            availability.showErrorNotification(getApplicationContext(), errorCode);
            Timber.w("Google play services needs install/update/enable");
        } else {
            // Google Play services is not available.
            Timber.e("Google play services not available");
        }
    }

    /**
     * A logging tree class which logs important information for crash reporting and feedback
     */
    private static class ReleaseTree extends Timber.DebugTree {

        PrintStream mLog;
        SimpleDateFormat mSdf;

        /**
         * Create Timber log tree that log to {@link PrintStream}
         * @param log Stream to log to
         */
        ReleaseTree(@NonNull PrintStream log) {
            mLog = log;
            mSdf = new SimpleDateFormat("dd/MM HH:mm:ss:SSS ", Locale.getDefault());
        }

        @Override
        protected boolean isLoggable(String tag, int priority) {
            return priority != Log.VERBOSE && priority != Log.DEBUG;
        }

        @Override
        protected void log(int priority, String tag, @NonNull String message, Throwable t) {
            StringBuilder sb = new StringBuilder();

            sb.append(mSdf.format(new Date()));
            switch (priority) {
            case Log.ERROR:
                sb.append("E/");
                break;
            case Log.WARN:
                sb.append("W/");
                break;
            case Log.INFO:
                sb.append("I/");
                break;
            case Log.ASSERT:
                sb.append("A/");
                break;
            }
            sb.append(tag);
            sb.append(": ");
            sb.append(message);
            sb.append('\n');

            mLog.append(sb.toString());
            if (t != null) {
                t.printStackTrace(mLog);
            }

            /* TODO send anonymous error info to some analytics service
            FakeCrashLibrary.log(priority, tag, message);
                
            if (t != null) {
            if (priority == Log.ERROR) {
                FakeCrashLibrary.logError(t);
            } else if (priority == Log.WARN) {
                FakeCrashLibrary.logWarning(t);
            }
            }
            */
        }
    }

    /**
     * Check Google Play services and shows error dialog to user if they are not installed
     *
     * @param activity Activity to show dialog withing and context source
     * @return true if services are NOW accessible, false otherwise
     */
    public static boolean checkPlayServices(@NonNull Activity activity) {
        GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance();
        int result = googleAPI.isGooglePlayServicesAvailable(activity);
        if (result != ConnectionResult.SUCCESS) {
            if (googleAPI.isUserResolvableError(result)) {
                googleAPI.getErrorDialog(activity, result, 9000).show();
            }

            return false;
        }
        return true;
    }
}