com.mycelium.wallet.activity.modern.ModernMain.java Source code

Java tutorial

Introduction

Here is the source code for com.mycelium.wallet.activity.modern.ModernMain.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.mycelium.wallet.activity.modern;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ShareCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.Log;
import android.view.*;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.mrd.bitlib.model.Address;
import com.mycelium.net.ServerEndpointType;
import com.mycelium.wallet.*;
import com.mycelium.wallet.activity.AboutActivity;
import com.mycelium.wallet.activity.ScanActivity;
import com.mycelium.wallet.activity.main.BalanceMasterFragment;
import com.mycelium.wallet.activity.main.TransactionHistoryFragment;
import com.mycelium.wallet.activity.send.InstantWalletActivity;
import com.mycelium.wallet.activity.settings.SettingsActivity;
import com.mycelium.wallet.coinapult.CoinapultAccount;
import com.mycelium.wallet.event.*;
import com.mycelium.wallet.external.cashila.activity.CashilaPaymentsActivity;
import com.mycelium.wallet.persistence.MetadataStorage;
import com.mycelium.wapi.api.response.Feature;
import com.mycelium.wapi.wallet.*;
import com.squareup.otto.Subscribe;
import de.cketti.library.changelog.ChangeLog;
import info.guardianproject.onionkit.ui.OrbotHelper;

import java.io.*;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

public class ModernMain extends ActionBarActivity {
    private static final int TAB_ID_ACCOUNTS = 0;
    private static final int TAB_ID_BALANCE = 1;
    private static final int TAB_ID_HISTORY = 2;

    private static final int REQUEST_SETTING_CHANGED = 5;
    public static final int GENERIC_SCAN_REQUEST = 4;
    public static final int MIN_AUTOSYNC_INTERVAL = (int) Constants.MS_PR_MINUTE;
    public static final int MIN_FULLSYNC_INTERVAL = (int) (5 * Constants.MS_PR_HOUR);
    public static final String LAST_SYNC = "LAST_SYNC";
    private static final String APP_START = "APP_START";
    private MbwManager _mbwManager;

    ViewPager mViewPager;
    TabsAdapter mTabsAdapter;
    ActionBar.Tab mBalanceTab;
    ActionBar.Tab mAccountsTab;
    private MenuItem refreshItem;
    private Toaster _toaster;
    private long _lastSync = 0;
    private boolean _isAppStart = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        _mbwManager = MbwManager.getInstance(this);
        mViewPager = new ViewPager(this);
        mViewPager.setId(R.id.pager);
        setContentView(mViewPager);
        ActionBar bar = getSupportActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        bar.setDisplayOptions(1, ActionBar.DISPLAY_SHOW_TITLE);

        // Load the theme-background (usually happens in styles.xml) but use a lower
        // pixel format, this saves around 10MB of allocated memory
        // persist the loaded Bitmap in the context of mbw-manager and reuse it every time this activity gets created
        try {
            BitmapDrawable background = (BitmapDrawable) _mbwManager.getBackgroundObjectsCache()
                    .get("mainBackground", new Callable<BitmapDrawable>() {
                        @Override
                        public BitmapDrawable call() throws Exception {
                            BitmapFactory.Options options = new BitmapFactory.Options();
                            options.inPreferredConfig = Bitmap.Config.RGB_565;
                            Bitmap background = BitmapFactory.decodeResource(getResources(),
                                    R.drawable.background_witherrors_dimmed, options);
                            BitmapDrawable drawable = new BitmapDrawable(getResources(), background);
                            drawable.setGravity(Gravity.CENTER);
                            return drawable;
                        }
                    });
            getWindow().setBackgroundDrawable(background);
        } catch (ExecutionException ignore) {
        }

        mTabsAdapter = new TabsAdapter(this, mViewPager, _mbwManager);
        mAccountsTab = bar.newTab();
        mTabsAdapter.addTab(mAccountsTab.setText(getString(R.string.tab_accounts)), AccountsFragment.class, null);
        mBalanceTab = bar.newTab();
        mTabsAdapter.addTab(mBalanceTab.setText(getString(R.string.tab_balance)), BalanceMasterFragment.class,
                null);
        mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.tab_transactions)),
                TransactionHistoryFragment.class, null);
        final Bundle addressBookConfig = new Bundle();
        addressBookConfig.putBoolean(AddressBookFragment.SELECT_ONLY, false);
        mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.tab_addresses)), AddressBookFragment.class,
                addressBookConfig);

        bar.selectTab(mBalanceTab);
        _toaster = new Toaster(this);

        ChangeLog cl = new DarkThemeChangeLog(this);
        if (cl.isFirstRun() && cl.getChangeLog(false).size() > 0 && !cl.isFirstRunEver()) {
            cl.getLogDialog().show();
        }

        checkTorState();

        if (savedInstanceState != null) {
            _lastSync = savedInstanceState.getLong(LAST_SYNC, 0);
            _isAppStart = savedInstanceState.getBoolean(APP_START, true);
        }

        if (_isAppStart) {
            _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.APP_START);
            checkGapBug();
            _isAppStart = false;
        }
    }

    private void checkGapBug() {
        final WalletManager walletManager = _mbwManager.getWalletManager(false);
        final List<Integer> gaps = walletManager.getGapsBug();
        if (!gaps.isEmpty()) {
            try {
                final List<Address> gapAddresses = walletManager.getGapAddresses(AesKeyCipher.defaultKeyCipher());
                final String gapsString = Joiner.on(", ").join(gapAddresses);
                Log.d("Gaps", gapsString);

                final SpannableString s = new SpannableString(
                        "Sorry to interrupt you... \n \nWe discovered a bug in the account logic that will make problems if you someday need to restore from your 12 word backup.\n\nFor further information see here: https://wallet.mycelium.com/info/gaps \n\nMay we try to resolve it for you? Press OK, to share one address per affected account with us.");
                Linkify.addLinks(s, Linkify.ALL);

                final AlertDialog d = new AlertDialog.Builder(this).setTitle("Account Gap").setMessage(s)

                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                createPlaceHolderAccounts(gaps);
                                _mbwManager.reportIgnoredException(
                                        new RuntimeException("Address gaps: " + gapsString));
                            }
                        }).setNegativeButton("Ignore", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        }).show();

                // Make the textview clickable. Must be called after show()
                ((TextView) d.findViewById(android.R.id.message))
                        .setMovementMethod(LinkMovementMethod.getInstance());

            } catch (KeyCipher.InvalidKeyCipher invalidKeyCipher) {
                throw new RuntimeException(invalidKeyCipher);
            }
        }
    }

    private void createPlaceHolderAccounts(List<Integer> gapIndex) {
        final WalletManager walletManager = _mbwManager.getWalletManager(false);
        for (Integer index : gapIndex) {
            try {
                final UUID newAccount = walletManager.createArchivedGapFiller(AesKeyCipher.defaultKeyCipher(),
                        index);
                _mbwManager.getMetadataStorage().storeAccountLabel(newAccount, "Gap Account " + (index + 1));
            } catch (KeyCipher.InvalidKeyCipher invalidKeyCipher) {
                throw new RuntimeException(invalidKeyCipher);
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putLong(LAST_SYNC, _lastSync);
        outState.putBoolean(APP_START, _isAppStart);
    }

    private void checkTorState() {
        if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR) {
            OrbotHelper obh = new OrbotHelper(this);
            // only check for Orbot if the OS is older than AndroidN (SDK_INT==24),
            // because the current check does not work any more
            // see: https://github.com/mycelium-com/wallet/issues/288#issuecomment-257261708
            if (!obh.isOrbotRunning(this) && android.os.Build.VERSION.SDK_INT < 24) {
                obh.requestOrbotStart(this);
            }
        }
    }

    @Override
    protected void onResume() {
        _mbwManager.getEventBus().register(this);

        // Start WAPI as a delayed action. This way we don't immediately block the account
        // while synchronizing
        Handler h = new Handler();
        if (_lastSync == 0 || new Date().getTime() - _lastSync > MIN_AUTOSYNC_INTERVAL) {
            h.postDelayed(new Runnable() {
                @Override
                public void run() {
                    _mbwManager.getVersionManager().checkForUpdate();
                    _mbwManager.getExchangeRateManager().requestRefresh();

                    // if the last full sync is too old (or not known), start a full sync for _all_ accounts
                    // otherwise just run a normal sync for the current account
                    final Optional<Long> lastFullSync = _mbwManager.getMetadataStorage().getLastFullSync();
                    if (lastFullSync.isPresent()
                            && (new Date().getTime() - lastFullSync.get() < MIN_FULLSYNC_INTERVAL)) {
                        _mbwManager.getWalletManager(false).startSynchronization();
                    } else {
                        _mbwManager.getWalletManager(false).startSynchronization(SyncMode.FULL_SYNC_ALL_ACCOUNTS);
                        _mbwManager.getMetadataStorage().setLastFullSync(new Date().getTime());
                    }
                }
            }, 70);
            _lastSync = new Date().getTime();
        }

        supportInvalidateOptionsMenu();
        super.onResume();
    }

    @Override
    protected void onPause() {
        _mbwManager.getEventBus().unregister(this);
        _mbwManager.getVersionManager().closeDialog();
        super.onPause();
    }

    @Override
    public void onBackPressed() {
        ActionBar bar = getSupportActionBar();
        if (bar.getSelectedTab() == mBalanceTab) {
            //         if(Build.VERSION.SDK_INT >= 21) {
            //            finishAndRemoveTask();
            //         } else {
            //            finish();
            //         }
            // this is not finishing on Android 6 LG G4, so the pin on startup is not requested.
            // commented out code above doesn't do the trick, neither.
            _mbwManager.setStartUpPinUnlocked(false);
            super.onBackPressed();
        } else {
            bar.selectTab(mBalanceTab);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.transaction_history_options_global, menu);
        inflater.inflate(R.menu.main_activity_options_menu, menu);
        addEnglishSetting(menu.findItem(R.id.miSettings));
        inflater.inflate(R.menu.refresh, menu);
        inflater.inflate(R.menu.export_history, menu);
        inflater.inflate(R.menu.record_options_menu_global, menu);
        inflater.inflate(R.menu.addressbook_options_global, menu);
        return true;
    }

    private void addEnglishSetting(MenuItem settingsItem) {
        String displayed = getResources().getString(R.string.settings);
        String settingsEn = Utils.loadEnglish(R.string.settings);
        if (!settingsEn.equals(displayed)) {
            settingsItem.setTitle(settingsItem.getTitle() + " (" + settingsEn + ")");
        }
    }

    // controlling the behavior here is the safe but slightly slower responding
    // way of doing this.
    // controlling the visibility from the individual fragments is a bug-ridden
    // nightmare.
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        final int tabIdx = mViewPager.getCurrentItem();

        // at the moment, we allow to make backups multiple times
        Preconditions.checkNotNull(menu.findItem(R.id.miBackup)).setVisible(true);

        // Add Record menu
        final boolean isAccountTab = tabIdx == TAB_ID_ACCOUNTS;
        final boolean locked = _mbwManager.isKeyManagementLocked();
        Preconditions.checkNotNull(menu.findItem(R.id.miAddRecord)).setVisible(isAccountTab && !locked);
        Preconditions.checkNotNull(menu.findItem(R.id.miAddFiatAccount)).setVisible(isAccountTab);

        // Lock menu
        final boolean hasPin = _mbwManager.isPinProtected();
        Preconditions.checkNotNull(menu.findItem(R.id.miLockKeys)).setVisible(isAccountTab && !locked && hasPin);

        // Refresh menu
        final boolean isBalanceTab = tabIdx == TAB_ID_BALANCE;
        final boolean isHistoryTab = tabIdx == TAB_ID_HISTORY;
        refreshItem = Preconditions.checkNotNull(menu.findItem(R.id.miRefresh));
        refreshItem.setVisible(isBalanceTab || isHistoryTab || isAccountTab);
        setRefreshAnimation();

        //export tx history
        Preconditions.checkNotNull(menu.findItem(R.id.miExportHistory)).setVisible(isHistoryTab);

        final boolean showSepaEntry = isBalanceTab && _mbwManager.getMetadataStorage().getCashilaIsEnabled();
        Preconditions.checkNotNull(menu.findItem(R.id.miSepaSend).setVisible(showSepaEntry));

        Preconditions.checkNotNull(menu.findItem(R.id.miRescanTransactions)).setVisible(isHistoryTab);

        final boolean isAddressBook = tabIdx == 3;
        Preconditions.checkNotNull(menu.findItem(R.id.miAddAddress)).setVisible(isAddressBook);

        return super.onPrepareOptionsMenu(menu);
    }

    @SuppressWarnings("unused")
    private boolean canObtainLocation() {
        final boolean hasFeature = getPackageManager().hasSystemFeature("android.hardware.location.network");
        if (!hasFeature) {
            return false;
        }
        String permission = "android.permission.ACCESS_COARSE_LOCATION";
        int res = checkCallingOrSelfPermission(permission);
        return (res == PackageManager.PERMISSION_GRANTED);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        final int itemId = item.getItemId();
        if (itemId == R.id.miColdStorage) {
            InstantWalletActivity.callMe(this);
            return true;
        } else if (itemId == R.id.miSettings) {
            Intent intent = new Intent(this, SettingsActivity.class);
            startActivityForResult(intent, REQUEST_SETTING_CHANGED);
            return true;
        } else if (itemId == R.id.miBackup) {
            Utils.pinProtectedWordlistBackup(this);
            return true;
            //with wordlists, we just need to backup and verify in one step
            //} else if (itemId == R.id.miVerifyBackup) {
            //   VerifyBackupActivity.callMe(this);
            //   return true;
        } else if (itemId == R.id.miRefresh) {
            //switch server every third time the refresh button gets hit
            if (new Random().nextInt(3) == 0) {
                _mbwManager.switchServer();
            }
            // every 5th manual refresh make a full scan
            if (new Random().nextInt(5) == 0) {
                _mbwManager.getWalletManager(false).startSynchronization(SyncMode.FULL_SYNC_CURRENT_ACCOUNT_FORCED);
            } else {
                if (mViewPager.getCurrentItem() == TAB_ID_ACCOUNTS) {
                    // if we are in the accounts tab, sync all accounts if the users forces a sync
                    _mbwManager.getWalletManager(false).startSynchronization(SyncMode.NORMAL_ALL_ACCOUNTS_FORCED);
                } else {
                    // only sync the current account
                    _mbwManager.getWalletManager(false).startSynchronization(SyncMode.NORMAL_FORCED);
                }
            }

            // also fetch a new exchange rate, if necessary
            _mbwManager.getExchangeRateManager().requestOptionalRefresh();
        } else if (itemId == R.id.miHelp) {
            openMyceliumHelp();
        } else if (itemId == R.id.miAbout) {
            Intent intent = new Intent(this, AboutActivity.class);
            startActivity(intent);
        } else if (itemId == R.id.miRescanTransactions) {
            _mbwManager.getSelectedAccount().dropCachedData();
            _mbwManager.getWalletManager(false).startSynchronization(SyncMode.FULL_SYNC_CURRENT_ACCOUNT_FORCED);
        } else if (itemId == R.id.miSepaSend) {
            _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.CASHILA, true, new Runnable() {
                @Override
                public void run() {
                    startActivity(CashilaPaymentsActivity.getIntent(ModernMain.this));
                }
            });
        } else if (itemId == R.id.miExportHistory) {
            shareTransactionHistory();
        }
        return super.onOptionsItemSelected(item);
    }

    private void shareTransactionHistory() {
        WalletAccount account = _mbwManager.getSelectedAccount();
        MetadataStorage metaData = _mbwManager.getMetadataStorage();
        try {
            String fileName = "MyceliumExport_" + System.currentTimeMillis() + ".csv";
            File historyData = DataExport.getTxHistoryCsv(account, metaData, getFileStreamPath(fileName));
            PackageManager packageManager = Preconditions.checkNotNull(getPackageManager());
            PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), PackageManager.GET_PROVIDERS);
            for (ProviderInfo info : packageInfo.providers) {
                if (info.name.equals("android.support.v4.content.FileProvider")) {
                    String authority = info.authority;
                    Uri uri = FileProvider.getUriForFile(this, authority, historyData);
                    Intent intent = ShareCompat.IntentBuilder.from(this).setStream(uri) // uri from FileProvider
                            .setType("text/plain")
                            .setSubject(getResources().getString(R.string.transaction_history_title))
                            .setText(getResources().getString(R.string.transaction_history_title)).getIntent()
                            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    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);
                    }
                    startActivity(Intent.createChooser(intent,
                            getResources().getString(R.string.share_transaction_history)));
                }
            }
        } catch (IOException | PackageManager.NameNotFoundException e) {
            _toaster.toast("Export failed. Check your logs", false);
            e.printStackTrace();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_SETTING_CHANGED) {
            // restart activity if language changes
            // or anything else in settings. this makes some of the listeners
            // obsolete
            Intent running = getIntent();
            finish();
            startActivity(running);
        } else if (requestCode == GENERIC_SCAN_REQUEST) {
            if (resultCode != RESULT_OK) {
                //report to user in case of error
                //if no scan handlers match successfully, this is the last resort to display an error msg
                ScanActivity.toastScanError(resultCode, data, this);
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    private void openMyceliumHelp() {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(Constants.MYCELIUM_WALLET_HELP_URL));
        startActivity(intent);
        Toast.makeText(this, R.string.going_to_mycelium_com_help, Toast.LENGTH_LONG).show();
    }

    public void setRefreshAnimation() {
        if (refreshItem != null) {
            if (_mbwManager.getWalletManager(false).getState() == WalletManager.State.SYNCHRONIZING) {
                MenuItem menuItem = MenuItemCompat.setActionView(refreshItem,
                        R.layout.actionbar_indeterminate_progress);
                ImageView ivTorIcon = (ImageView) menuItem.getActionView().findViewById(R.id.ivTorIcon);

                if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR
                        && _mbwManager.getTorManager() != null) {
                    ivTorIcon.setVisibility(View.VISIBLE);
                    if (_mbwManager.getTorManager().getInitState() == 100) {
                        ivTorIcon.setImageResource(R.drawable.tor);
                    } else {
                        ivTorIcon.setImageResource(R.drawable.tor_gray);
                    }
                } else {
                    ivTorIcon.setVisibility(View.GONE);
                }

            } else {
                MenuItemCompat.setActionView(refreshItem, null);
            }
        }
    }

    @Subscribe
    public void syncStarted(SyncStarted event) {
        setRefreshAnimation();
    }

    @Subscribe
    public void syncStopped(SyncStopped event) {
        setRefreshAnimation();
    }

    @Subscribe
    public void torState(TorStateChanged event) {
        setRefreshAnimation();
    }

    @Subscribe
    public void synchronizationFailed(SyncFailed event) {
        _toaster.toastConnectionError();
    }

    @Subscribe
    public void transactionBroadcasted(TransactionBroadcasted event) {
        _toaster.toast(R.string.transaction_sent, false);
    }

    @Subscribe
    public void onNewFeatureWarnings(final FeatureWarningsAvailable event) {
        _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.MAIN_SCREEN);

        if (_mbwManager.getSelectedAccount() instanceof CoinapultAccount) {
            _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.COINAPULT);
        }
    }

    @Subscribe
    public void onNewVersion(final NewWalletVersionAvailable event) {
        _mbwManager.getVersionManager().showIfRelevant(event.versionInfo, this);
    }
}