com.brq.wallet.activity.export.BackupToPdfActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.brq.wallet.activity.export.BackupToPdfActivity.java

Source

/*
 * Copyright 2013, 2014 Megion Research and Development GmbH
 *
 * Licensed under the Microsoft Reference Source License (MS-RSL)
 *
 * This license governs use of the accompanying software. If you use the software, you accept this license.
 * If you do not accept the license, do not use the software.
 *
 * 1. Definitions
 * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
 * "You" means the licensee of the software.
 * "Your company" means the company you worked for when you downloaded the software.
 * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes
 * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the
 * software, and specifically excludes the right to distribute the software outside of your company.
 * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor
 * under this license.
 *
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free copyright license to reproduce the software for reference use.
 * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free patent license under licensed patents for reference use.
 *
 * 3. Limitations
 * (A) No Trademark License- This license does not grant you any rights to use the Licensors name, logo, or trademarks.
 * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software
 * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
 * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties,
 * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot
 * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability,
 * fitness for a particular purpose and non-infringement.
 */

package com.brq.wallet.activity.export;

import android.app.Activity;
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.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.app.ShareCompat;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.TextView;

import com.brq.wallet.AndroidRandomSource;
import com.brq.wallet.MbwManager;
import com.brq.wallet.UserFacingException;
import com.brq.wallet.Utils;
import com.brq.wallet.service.ServiceTaskStatusEx;
import com.brq.wallet.service.TaskExecutionServiceController;
import com.google.common.base.Preconditions;
import com.mrd.bitlib.crypto.MrdExport;
import com.mrd.bitlib.crypto.MrdExport.V1.KdfParameters;
import com.brq.wallet.*;
import com.brq.wallet.service.CreateMrdBackupTask;
import com.brq.wallet.service.ServiceTask;
import com.mycelium.wapi.wallet.AesKeyCipher;

//todo HD: export master seed without address/xpub extra data.
//todo HD: later: be compatible with a common format
import java.io.File;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import static android.text.format.DateFormat.getDateFormat;

public class BackupToPdfActivity extends Activity
        implements TaskExecutionServiceController.TaskExecutionServiceCallback {

    private static final String MYCELIUM_EXPORT_FOLDER = "mycelium";

    public static void callMe(Activity currentActivity) {
        Intent intent = new Intent(currentActivity, BackupToPdfActivity.class);
        currentActivity.startActivity(intent);
    }

    private static final String FILE_NAME_PREFIX = "mycelium-backup";

    private static final int SHARE_REQUEST_CODE = 1;

    private MbwManager _mbwManager;
    private long _backupTime;
    private String _fileName;
    private String _password;
    private ProgressUpdater _progressUpdater;
    private TaskExecutionServiceController _taskExecutionServiceController;
    private ServiceTaskStatusEx _taskStatus;
    private boolean _isPdfGenerated;
    private boolean _oomDetected;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.export_to_pdf_activity);
        Utils.preventScreenshots(this);

        _mbwManager = MbwManager.getInstance(this.getApplication());

        // Load saved state
        if (savedInstanceState != null) {
            _backupTime = savedInstanceState.getLong("backupTime", 0);
            _password = savedInstanceState.getString("password");
            _isPdfGenerated = savedInstanceState.getBoolean("isPdfGenerated");
        }

        if (_backupTime == 0) {
            _backupTime = new Date().getTime();
        }

        if (_password == null) {
            _password = MrdExport.V1.generatePassword(new AndroidRandomSource()).toUpperCase(Locale.US);
        }

        _fileName = getExportFileName(_backupTime);
        _taskExecutionServiceController = new TaskExecutionServiceController();

        // Populate Password
        ((TextView) findViewById(R.id.tvPassword)).setText(splitPassword(_password));

        // Populate Checksum
        char checksumChar = MrdExport.V1.calculatePasswordChecksum(_password);
        String checksumString = ("  " + checksumChar).toUpperCase(Locale.US);
        ((TextView) findViewById(R.id.tvChecksum)).setText(checksumString);

        findViewById(R.id.btSharePdf).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                sharePdf();
            }

        });

        _progressUpdater = new ProgressUpdater();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putLong("backupTime", _backupTime);
        outState.putString("password", _password);
        outState.putBoolean("isPdfGenerated", _isPdfGenerated);
        super.onSaveInstanceState(outState);
    }

    private String splitPassword(String password) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (int i = 0; i < password.length(); i++) {
            if (first) {
                first = false;
            } else {
                if (i % 3 == 0) {
                    sb.append(' ');
                }
            }
            sb.append(password.charAt(i));
        }
        return sb.toString();
    }

    @Override
    protected void onResume() {
        if (!_isPdfGenerated) {
            startTask();
        } else {
            enableSharing();
        }

        _progressUpdater.start();
        super.onResume();
    }

    class ProgressUpdater implements Runnable {
        final Handler _handler;

        ProgressUpdater() {
            _handler = new Handler();
        }

        public void start() {
            _handler.post(this);
        }

        public void stop() {
            _handler.removeCallbacks(this);
        }

        /**
         * Update the percentage of work completed for key stretching and pdf
         * generation
         */
        @Override
        public void run() {

            if (_oomDetected) {
                ((TextView) findViewById(R.id.tvProgress)).setText("");
                ((TextView) findViewById(R.id.tvStatus)).setText(R.string.out_of_memory_error);
                return;
            }

            if (_isPdfGenerated) {
                ((TextView) findViewById(R.id.tvProgress)).setText("");
                ((TextView) findViewById(R.id.tvStatus)).setText(R.string.encrypted_pdf_backup_document_ready);
                return;
            }

            if (_taskStatus == null) {
                ((TextView) findViewById(R.id.tvProgress)).setText("");
                ((TextView) findViewById(R.id.tvStatus)).setText("");
                _taskExecutionServiceController.requestStatus();
            } else {
                if (_taskStatus.state != ServiceTaskStatusEx.State.FINISHED) {
                    _taskExecutionServiceController.requestStatus();
                }
                ((TextView) findViewById(R.id.tvProgress)).setText("" + (int) (_taskStatus.progress * 100) + "%");
                ((TextView) findViewById(R.id.tvStatus)).setText(_taskStatus.statusMessage);
            }

            // Reschedule
            _handler.postDelayed(this, 300);
        }
    }

    @Override
    protected void onPause() {
        _progressUpdater.stop();
        super.onPause();
    }

    @SuppressWarnings("unused")
    private void deleteBackupFile() {
        boolean success;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            success = this.deleteFile(getFullExportFilePath());
        } else {
            success = new File(getFullExportFilePath()).delete();
        }
        Log.i("BackupToPdfActivity", "Successfully deleted backup file: " + success);
    }

    @Override
    protected void onDestroy() {
        _taskExecutionServiceController.terminate();
        _taskExecutionServiceController.unbind(this);
        super.onDestroy();
    }

    private String getExportFileName(long exportTime) {
        Date exportDate = new Date(exportTime);
        Locale locale = getResources().getConfiguration().locale;
        String hourString = DateFormat.getDateInstance(DateFormat.SHORT, locale).format(exportDate);
        hourString = replaceInvalidFileNameChars(hourString);
        String dateString = DateFormat.getTimeInstance(DateFormat.SHORT, locale).format(exportDate);
        dateString = replaceInvalidFileNameChars(dateString);
        return FILE_NAME_PREFIX + '-' + hourString + '-' + dateString + ".pdf";
    }

    private static String replaceInvalidFileNameChars(String name) {
        return name.replace(':', '.').replace(' ', '-').replace('\\', '-').replace('/', '-').replace('*', '-')
                .replace('?', '-').replace('"', '-').replace('\'', '-').replace('<', '-').replace('>', '-')
                .replace('|', '-');
    }

    private String getFullExportFilePath() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return _fileName;
        } else {
            File externalStorageDir = Environment.getExternalStorageDirectory();
            String directory = externalStorageDir.getAbsolutePath() + File.separatorChar + MYCELIUM_EXPORT_FOLDER;
            File dirFile = new File(directory);
            if (!dirFile.exists()) {
                boolean wasCreated = dirFile.mkdirs();
                Preconditions.checkArgument(wasCreated);
            } else {
                Preconditions.checkArgument(dirFile.isDirectory());
            }
            return directory + File.separatorChar + _fileName;
        }
    }

    private void startTask() {
        findViewById(R.id.btSharePdf).setEnabled(false);
        KdfParameters kdfParameters = KdfParameters.createNewFromPassphrase(_password, new AndroidRandomSource(),
                _mbwManager.getDeviceScryptParameters());
        CreateMrdBackupTask task = new CreateMrdBackupTask(kdfParameters, this.getApplicationContext(),
                _mbwManager.getWalletManager(false), AesKeyCipher.defaultKeyCipher(),
                _mbwManager.getMetadataStorage(), _mbwManager.getNetwork(), getFullExportFilePath());
        _taskExecutionServiceController.bind(this, this);
        _taskExecutionServiceController.start(task);
    }

    private void enableSharing() {
        findViewById(R.id.btSharePdf).setEnabled(true);
        ((TextView) findViewById(R.id.tvStatus)).setText(R.string.encrypted_pdf_backup_document_ready);
    }

    private void sharePdf() {
        findViewById(R.id.btSharePdf).setEnabled(false);
        ((TextView) findViewById(R.id.tvStatus))
                .setText(getResources().getString(R.string.encrypted_pdf_backup_sharing));
        Uri uri = getUri();

        String bodyText = getResources().getString(R.string.encrypted_pdf_backup_email_text);
        Intent intent = ShareCompat.IntentBuilder.from(this).setStream(uri)
                // uri from FileProvider
                .setType("application/pdf").setSubject(getSubject()).setText(bodyText).getIntent()
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        grantPermissions(intent, uri);
        startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.share_with)),
                SHARE_REQUEST_CODE);
    }

    private String getSubject() {
        Date now = new Date();
        Context appContext = Preconditions.checkNotNull(getApplicationContext());
        DateFormat dateFormat = getDateFormat(appContext);
        return getResources().getString(R.string.encrypted_pdf_backup_email_title) + " " + dateFormat.format(now);
    }

    private void grantPermissions(Intent intent, Uri uri) {
        // grant permissions for all apps that can handle given intent
        // if we know of any malicious app that tries to intercept this,
        // we could block it here based on packageName
        PackageManager packageManager = Preconditions.checkNotNull(getPackageManager());
        List<ResolveInfo> resInfoList = packageManager.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }

    private Uri getUri() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            String authority = getFileProviderAuthority();
            return FileProvider.getUriForFile(this, authority, getFileStreamPath(getFullExportFilePath()));
        } else {
            return Uri.fromFile(new File(getFullExportFilePath()));
        }
    }

    // ignore null checks in this method
    private String getFileProviderAuthority() {
        try {
            PackageManager packageManager = getApplication().getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), PackageManager.GET_PROVIDERS);
            for (ProviderInfo info : packageInfo.providers) {
                if (info.name.equals("android.support.v4.content.FileProvider")) {
                    return info.authority;
                }
            }
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        throw new RuntimeException("No file provider authority specified in manifest");
    }

    @Override
    public void onStatusReceived(ServiceTaskStatusEx status) {
        _taskStatus = status;
        if (_taskStatus != null && _taskStatus.state == ServiceTaskStatusEx.State.FINISHED) {
            _taskExecutionServiceController.requestResult();
        }
    }

    @Override
    public void onResultReceived(ServiceTask<?> result) {
        CreateMrdBackupTask task = (CreateMrdBackupTask) result;
        try {
            _isPdfGenerated = task.getResult();
        } catch (UserFacingException e) {
            _oomDetected = true;
            _mbwManager.reportIgnoredException(e);
        }
        if (_isPdfGenerated) {
            enableSharing();
        }
    }

}