de.schildbach.wallet.ui.AlertDialogsFragment.java Source code

Java tutorial

Introduction

Here is the source code for de.schildbach.wallet.ui.AlertDialogsFragment.java

Source

/*
 * Copyright the original author or authors.
 *
 * 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 de.schildbach.wallet.ui;

import java.io.BufferedReader;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Splitter;
import com.google.common.base.Strings;

import de.schildbach.wallet.Constants;
import de.schildbach.wallet.R;
import de.schildbach.wallet.WalletApplication;
import de.schildbach.wallet.util.CrashReporter;

import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.format.DateUtils;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;

/**
 * @author Andreas Schildbach
 */
public class AlertDialogsFragment extends Fragment {
    private static final String FRAGMENT_TAG = AlertDialogsFragment.class.getName();

    public static void add(final FragmentManager fm) {
        Fragment fragment = fm.findFragmentByTag(FRAGMENT_TAG);
        if (fragment == null) {
            fragment = new AlertDialogsFragment();
            fm.beginTransaction().add(fragment, FRAGMENT_TAG).commit();
        }
    }

    private AbstractWalletActivity activity;
    private WalletApplication application;
    private PackageManager packageManager;

    private HttpUrl versionUrl;

    private final Handler handler = new Handler();
    private HandlerThread backgroundThread;
    private Handler backgroundHandler;

    private static final Logger log = LoggerFactory.getLogger(AlertDialogsFragment.class);

    @Override
    public void onAttach(final Context context) {
        super.onAttach(context);
        this.activity = (AbstractWalletActivity) context;
        this.application = activity.getWalletApplication();
        this.packageManager = activity.getPackageManager();
    }

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        backgroundThread = new HandlerThread("backgroundThread", Process.THREAD_PRIORITY_BACKGROUND);
        backgroundThread.start();
        backgroundHandler = new Handler(backgroundThread.getLooper());

        final PackageInfo packageInfo = application.packageInfo();
        final int versionNameSplit = packageInfo.versionName.indexOf('-');
        final HttpUrl.Builder url = HttpUrl
                .parse(Constants.VERSION_URL
                        + (versionNameSplit >= 0 ? packageInfo.versionName.substring(versionNameSplit) : ""))
                .newBuilder();
        url.addEncodedQueryParameter("package", packageInfo.packageName);
        url.addEncodedQueryParameter("installer",
                Strings.nullToEmpty(packageManager.getInstallerPackageName(packageInfo.packageName)));
        url.addQueryParameter("current", Integer.toString(packageInfo.versionCode));
        versionUrl = url.build();
    }

    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        log.debug("querying \"{}\"...", versionUrl);
        final Request.Builder request = new Request.Builder();
        request.url(versionUrl);
        request.header("Accept-Charset", "utf-8");
        final String userAgent = application.httpUserAgent();
        if (userAgent != null)
            request.header("User-Agent", userAgent);

        final Call call = Constants.HTTP_CLIENT.newCall(request.build());

        backgroundHandler.post(new Runnable() {
            @Override
            public void run() {
                boolean abort = false;
                try {
                    final Response response = call.execute();
                    if (response.isSuccessful()) {
                        final long serverTime = response.headers().getDate("Date").getTime();
                        try (final BufferedReader reader = new BufferedReader(response.body().charStream())) {
                            abort = handleServerTime(serverTime);

                            while (true) {
                                final String line = reader.readLine();
                                if (line == null)
                                    break;
                                if (line.charAt(0) == '#')
                                    continue;

                                final Splitter splitter = Splitter.on('=').trimResults();
                                final Iterator<String> split = splitter.split(line).iterator();
                                if (!split.hasNext())
                                    continue;
                                final String key = split.next();
                                if (!split.hasNext()) {
                                    abort = handleLine(key);
                                    if (abort)
                                        break;
                                    continue;
                                }
                                final String value = split.next();
                                if (!split.hasNext()) {
                                    abort = handleProperty(key, value);
                                    if (abort)
                                        break;
                                    continue;
                                }
                                log.info("Ignoring line: {}", line);
                            }
                        }
                    }
                } catch (final Exception x) {
                    handleException(x);
                }
                if (!abort)
                    handleCatchAll();
            }
        });
    }

    @Override
    public void onDestroy() {
        backgroundThread.getLooper().quit();

        super.onDestroy();
    }

    private boolean handleLine(final String line) {
        final int serverVersionCode = Integer.parseInt(line.split("\\s+")[0]);
        log.info("according to \"" + versionUrl + "\", strongly recommended minimum app version is "
                + serverVersionCode);

        if (serverVersionCode > application.packageInfo().versionCode) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (isAdded())
                        createVersionAlertDialog().show();
                }
            });
            return true;
        }
        return false;
    }

    private boolean handleProperty(final String key, final String value) {
        if (key.equalsIgnoreCase("min.security_patch.bluetooth")) {
            final String minSecurityPatchLevel = value;
            log.info("according to \"{}\", minimum security patch level for bluetooth is {}", versionUrl,
                    minSecurityPatchLevel);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                    && Build.VERSION.SECURITY_PATCH.compareTo(minSecurityPatchLevel) < 0) {
                final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                if (bluetoothAdapter != null && BluetoothAdapter.getDefaultAdapter().isEnabled()) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (isAdded())
                                createInsecureBluetoothAlertDialog(minSecurityPatchLevel).show();
                        }
                    });
                    return true;
                }
            }
        } else {
            log.info("Ignoring key: {}", key);
        }
        return false;
    }

    private boolean handleServerTime(final long serverTime) {
        if (serverTime > 0) {
            final long diffMinutes = Math
                    .abs((System.currentTimeMillis() - serverTime) / DateUtils.MINUTE_IN_MILLIS);

            if (diffMinutes >= 60) {
                log.info("according to \"" + versionUrl + "\", system clock is off by " + diffMinutes + " minutes");
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (isAdded())
                            createTimeskewAlertDialog(diffMinutes).show();
                    }
                });
                return true;
            }
        }
        return false;
    }

    private boolean handleCatchAll() {
        final Intent stickyIntent = activity.registerReceiver(null,
                new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
        if (stickyIntent != null) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (isAdded())
                        createLowStorageAlertDialog().show();
                }
            });
            return true;
        }
        return false;
    }

    private void handleException(final Exception x) {
        if (x instanceof UnknownHostException || x instanceof SocketException
                || x instanceof SocketTimeoutException) {
            // swallow
            log.debug("problem reading", x);
        } else {
            CrashReporter.saveBackgroundTrace(new RuntimeException(versionUrl.toString(), x),
                    application.packageInfo());
        }
    }

    private Dialog createTimeskewAlertDialog(final long diffMinutes) {
        final Intent settingsIntent = new Intent(android.provider.Settings.ACTION_DATE_SETTINGS);
        final DialogBuilder dialog = DialogBuilder.warn(activity, R.string.wallet_timeskew_dialog_title);
        dialog.setMessage(getString(R.string.wallet_timeskew_dialog_msg, diffMinutes));
        if (packageManager.resolveActivity(settingsIntent, 0) != null) {
            dialog.setPositiveButton(R.string.button_settings, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(final DialogInterface dialog, final int id) {
                    try {
                        startActivity(settingsIntent);
                        activity.finish();
                    } catch (final Exception x) {
                        createSettingsFailedDialog(x.getMessage()).show();
                    }
                }
            });
        }
        dialog.setNegativeButton(R.string.button_dismiss, null);
        return dialog.create();
    }

    private Dialog createVersionAlertDialog() {
        final Intent marketIntent = new Intent(Intent.ACTION_VIEW,
                Uri.parse(String.format(Constants.MARKET_APP_URL, application.packageInfo().packageName)));
        final Intent binaryIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.BINARY_URL));

        final DialogBuilder dialog = DialogBuilder.warn(activity, R.string.wallet_version_dialog_title);
        final StringBuilder message = new StringBuilder(getString(R.string.wallet_version_dialog_msg));
        if (Build.VERSION.SDK_INT < Constants.SDK_DEPRECATED_BELOW)
            message.append("\n\n").append(getString(R.string.wallet_version_dialog_msg_deprecated));
        dialog.setMessage(message);

        if (packageManager.resolveActivity(marketIntent, 0) != null) {
            dialog.setPositiveButton(R.string.wallet_version_dialog_button_market,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(final DialogInterface dialog, final int id) {
                            startActivity(marketIntent);
                            activity.finish();
                        }
                    });
        }

        if (packageManager.resolveActivity(binaryIntent, 0) != null) {
            dialog.setNeutralButton(R.string.wallet_version_dialog_button_binary,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(final DialogInterface dialog, final int id) {
                            startActivity(binaryIntent);
                            activity.finish();
                        }
                    });
        }

        dialog.setNegativeButton(R.string.button_dismiss, null);
        return dialog.create();
    }

    private Dialog createInsecureBluetoothAlertDialog(final String minSecurityPatch) {
        final Intent settingsIntent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
        final DialogBuilder dialog = DialogBuilder.warn(activity,
                R.string.alert_dialogs_fragment_insecure_bluetooth_title);
        dialog.setMessage(getString(R.string.alert_dialogs_fragment_insecure_bluetooth_message, minSecurityPatch));
        if (packageManager.resolveActivity(settingsIntent, 0) != null) {
            dialog.setPositiveButton(R.string.button_settings, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(final DialogInterface dialog, final int id) {
                    try {
                        startActivity(settingsIntent);
                        activity.finish();
                    } catch (final Exception x) {
                        createSettingsFailedDialog(x.getMessage()).show();
                    }
                }
            });
        }
        dialog.setNegativeButton(R.string.button_dismiss, null);
        return dialog.create();
    }

    private Dialog createLowStorageAlertDialog() {
        final Intent settingsIntent = new Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
        final DialogBuilder dialog = DialogBuilder.warn(activity, R.string.wallet_low_storage_dialog_title);
        dialog.setMessage(R.string.wallet_low_storage_dialog_msg);
        if (packageManager.resolveActivity(settingsIntent, 0) != null) {
            dialog.setPositiveButton(R.string.wallet_low_storage_dialog_button_apps,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(final DialogInterface dialog, final int id) {
                            try {
                                startActivity(settingsIntent);
                                activity.finish();
                            } catch (final Exception x) {
                                createSettingsFailedDialog(x.getMessage()).show();
                            }
                        }
                    });
        }
        dialog.setNegativeButton(R.string.button_dismiss, null);
        return dialog.create();
    }

    private Dialog createSettingsFailedDialog(final String exceptionMessage) {
        final DialogBuilder dialog = new DialogBuilder(activity);
        dialog.setTitle(R.string.alert_dialogs_fragment_settings_failed_title);
        dialog.setMessage(exceptionMessage);
        dialog.singleDismissButton(null);
        return dialog.create();
    }
}