You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1072 lines
38 KiB
1072 lines
38 KiB
/*
|
|
* Copyright (c) 2017 m2049r
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.wownero.wownerujo;
|
|
|
|
import android.app.AlertDialog;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.PowerManager;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.design.widget.NavigationView;
|
|
import android.support.v4.app.Fragment;
|
|
import android.support.v4.app.FragmentManager;
|
|
import android.support.v4.app.FragmentTransaction;
|
|
import android.support.v4.view.GravityCompat;
|
|
import android.support.v4.widget.DrawerLayout;
|
|
import android.support.v7.app.ActionBarDrawerToggle;
|
|
import android.view.KeyEvent;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.widget.EditText;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.wownero.wownerujo.data.BarcodeData;
|
|
import com.wownero.wownerujo.data.TxData;
|
|
import com.wownero.wownerujo.dialog.CreditsFragment;
|
|
import com.wownero.wownerujo.dialog.HelpFragment;
|
|
import com.wownero.wownerujo.fragment.send.SendAddressWizardFragment;
|
|
import com.wownero.wownerujo.fragment.send.SendFragment;
|
|
import com.wownero.wownerujo.model.PendingTransaction;
|
|
import com.wownero.wownerujo.model.TransactionInfo;
|
|
import com.wownero.wownerujo.model.Wallet;
|
|
import com.wownero.wownerujo.model.WalletManager;
|
|
import com.wownero.wownerujo.service.WalletService;
|
|
import com.wownero.wownerujo.util.Helper;
|
|
import com.wownero.wownerujo.util.UserNotes;
|
|
import com.wownero.wownerujo.widget.Toolbar;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import timber.log.Timber;
|
|
|
|
public class WalletActivity extends SecureActivity implements WalletFragment.Listener,
|
|
WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
|
|
GenerateReviewFragment.ListenerWithWallet,
|
|
GenerateReviewFragment.Listener,
|
|
GenerateReviewFragment.PasswordChangedListener,
|
|
ScannerFragment.OnScannedListener, ReceiveFragment.Listener,
|
|
SendAddressWizardFragment.OnScanListener,
|
|
WalletFragment.DrawerLocker,
|
|
NavigationView.OnNavigationItemSelectedListener {
|
|
|
|
public static final String REQUEST_ID = "id";
|
|
public static final String REQUEST_PW = "pw";
|
|
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
|
|
|
private NavigationView accountsView;
|
|
private DrawerLayout drawer;
|
|
private ActionBarDrawerToggle drawerToggle;
|
|
|
|
private Toolbar toolbar;
|
|
private boolean needVerifyIdentity;
|
|
|
|
private String password;
|
|
|
|
@Override
|
|
public void onPasswordChanged(String newPassword) {
|
|
password = newPassword;
|
|
}
|
|
|
|
@Override
|
|
public String getPassword() {
|
|
return password;
|
|
}
|
|
|
|
@Override
|
|
public void setToolbarButton(int type) {
|
|
toolbar.setButton(type);
|
|
}
|
|
|
|
@Override
|
|
public void setTitle(String title, String subtitle) {
|
|
toolbar.setTitle(title, subtitle);
|
|
}
|
|
|
|
@Override
|
|
public void setTitle(String title) {
|
|
Timber.d("setTitle:%s.", title);
|
|
toolbar.setTitle(title);
|
|
}
|
|
|
|
@Override
|
|
public void setSubtitle(String subtitle) {
|
|
toolbar.setSubtitle(subtitle);
|
|
}
|
|
|
|
private boolean synced = false;
|
|
|
|
@Override
|
|
public boolean isSynced() {
|
|
return synced;
|
|
}
|
|
|
|
@Override
|
|
public boolean isWatchOnly() {
|
|
return getWallet().isWatchOnly();
|
|
}
|
|
|
|
@Override
|
|
public String getTxKey(String txId) {
|
|
return getWallet().getTxKey(txId);
|
|
}
|
|
|
|
@Override
|
|
public String getTxNotes(String txId) {
|
|
return getWallet().getUserNote(txId);
|
|
}
|
|
|
|
@Override
|
|
protected void onStart() {
|
|
super.onStart();
|
|
Timber.d("onStart()");
|
|
}
|
|
|
|
private void startWalletService() {
|
|
Bundle extras = getIntent().getExtras();
|
|
if (extras != null) {
|
|
acquireWakeLock();
|
|
String walletId = extras.getString(REQUEST_ID);
|
|
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
|
|
password = extras.getString(REQUEST_PW);
|
|
connectWalletService(walletId, password);
|
|
} else {
|
|
finish();
|
|
//throw new IllegalStateException("No extras passed! Panic!");
|
|
}
|
|
}
|
|
|
|
private void stopWalletService() {
|
|
disconnectWalletService();
|
|
releaseWakeLock();
|
|
}
|
|
|
|
@Override
|
|
protected void onStop() {
|
|
Timber.d("onStop()");
|
|
super.onStop();
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
Timber.d("onDestroy()");
|
|
if ((mBoundService != null) && (getWallet() != null)) {
|
|
saveWallet();
|
|
}
|
|
stopWalletService();
|
|
if (drawer != null) drawer.removeDrawerListener(drawerToggle);
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
public boolean hasWallet() {
|
|
return haveWallet;
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
|
if (renameItem != null)
|
|
renameItem.setVisible(hasWallet() && getWallet().isSynchronized());
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case R.id.action_info:
|
|
onWalletDetails();
|
|
return true;
|
|
case R.id.action_credits:
|
|
CreditsFragment.display(getSupportFragmentManager());
|
|
return true;
|
|
case R.id.action_share:
|
|
onShareTxInfo();
|
|
return true;
|
|
case R.id.action_help_tx_info:
|
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_tx_details);
|
|
return true;
|
|
case R.id.action_help_wallet:
|
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_wallet);
|
|
return true;
|
|
case R.id.action_details_help:
|
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
|
|
return true;
|
|
case R.id.action_details_changepw:
|
|
onWalletChangePassword();
|
|
return true;
|
|
case R.id.action_help_send:
|
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_send);
|
|
return true;
|
|
case R.id.action_rename:
|
|
onAccountRename();
|
|
return true;
|
|
default:
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
}
|
|
|
|
public void onWalletChangePassword() {
|
|
try {
|
|
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
AlertDialog dialog = detailsFragment.createChangePasswordDialog();
|
|
if (dialog != null) {
|
|
Helper.showKeyboard(dialog);
|
|
dialog.show();
|
|
}
|
|
} catch (ClassCastException ex) {
|
|
Timber.w("onWalletChangePassword() called, but no GenerateReviewFragment active");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
Timber.d("onCreate()");
|
|
super.onCreate(savedInstanceState);
|
|
if (savedInstanceState != null) {
|
|
// activity restarted
|
|
// we don't want that - finish it and fall back to previous activity
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
setContentView(R.layout.activity_wallet);
|
|
toolbar = (Toolbar) findViewById(R.id.toolbar);
|
|
setSupportActionBar(toolbar);
|
|
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
|
|
|
toolbar.setOnButtonListener(new Toolbar.OnButtonListener() {
|
|
@Override
|
|
public void onButton(int type) {
|
|
switch (type) {
|
|
case Toolbar.BUTTON_BACK:
|
|
onDisposeRequest();
|
|
onBackPressed();
|
|
break;
|
|
case Toolbar.BUTTON_CANCEL:
|
|
onDisposeRequest();
|
|
WalletActivity.super.onBackPressed();
|
|
break;
|
|
case Toolbar.BUTTON_CLOSE:
|
|
finish();
|
|
break;
|
|
case Toolbar.BUTTON_CREDITS:
|
|
Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show();
|
|
case Toolbar.BUTTON_NONE:
|
|
default:
|
|
Timber.e("Button " + type + "pressed - how can this be?");
|
|
}
|
|
}
|
|
});
|
|
|
|
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
|
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0);
|
|
drawer.addDrawerListener(drawerToggle);
|
|
drawerToggle.syncState();
|
|
setDrawerEnabled(false); // disable until synced
|
|
|
|
accountsView = (NavigationView) findViewById(R.id.accounts_nav);
|
|
accountsView.setNavigationItemSelectedListener(this);
|
|
|
|
showNet();
|
|
|
|
Fragment walletFragment = new WalletFragment();
|
|
getSupportFragmentManager().beginTransaction()
|
|
.add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()).commit();
|
|
Timber.d("fragment added");
|
|
|
|
startWalletService();
|
|
Timber.d("onCreate() done.");
|
|
}
|
|
|
|
public void showNet() {
|
|
switch (WalletManager.getInstance().getNetworkType()) {
|
|
case NetworkType_Mainnet:
|
|
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
|
|
break;
|
|
case NetworkType_Testnet:
|
|
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
|
|
break;
|
|
case NetworkType_Stagenet:
|
|
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
|
|
break;
|
|
default:
|
|
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
|
|
}
|
|
}
|
|
|
|
public Wallet getWallet() {
|
|
if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
|
|
return mBoundService.getWallet();
|
|
}
|
|
|
|
private WalletService mBoundService = null;
|
|
private boolean mIsBound = false;
|
|
|
|
private ServiceConnection mConnection = new ServiceConnection() {
|
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
// This is called when the connection with the service has been
|
|
// established, giving us the service object we can use to
|
|
// interact with the service. Because we have bound to a explicit
|
|
// service that we know is running in our own process, we can
|
|
// cast its IBinder to a concrete class and directly access it.
|
|
mBoundService = ((WalletService.WalletServiceBinder) service).getService();
|
|
mBoundService.setObserver(WalletActivity.this);
|
|
Bundle extras = getIntent().getExtras();
|
|
if (extras != null) {
|
|
String walletId = extras.getString(REQUEST_ID);
|
|
if (walletId != null) {
|
|
setTitle(walletId, getString(R.string.status_wallet_connecting));
|
|
}
|
|
}
|
|
updateProgress();
|
|
Timber.d("CONNECTED");
|
|
}
|
|
|
|
public void onServiceDisconnected(ComponentName className) {
|
|
// This is called when the connection with the service has been
|
|
// unexpectedly disconnected -- that is, its process crashed.
|
|
// Because it is running in our same process, we should never
|
|
// see this happen.
|
|
mBoundService = null;
|
|
setTitle(getString(R.string.wallet_activity_name), getString(R.string.status_wallet_disconnected));
|
|
Timber.d("DISCONNECTED");
|
|
}
|
|
};
|
|
|
|
void connectWalletService(String walletName, String walletPassword) {
|
|
// Establish a connection with the service. We use an explicit
|
|
// class name because we want a specific service implementation that
|
|
// we know will be running in our own process (and thus won't be
|
|
// supporting component replacement by other applications).
|
|
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
|
intent.putExtra(WalletService.REQUEST_WALLET, walletName);
|
|
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_LOAD);
|
|
intent.putExtra(WalletService.REQUEST_CMD_LOAD_PW, walletPassword);
|
|
startService(intent);
|
|
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
|
mIsBound = true;
|
|
Timber.d("BOUND");
|
|
}
|
|
|
|
void disconnectWalletService() {
|
|
if (mIsBound) {
|
|
// Detach our existing connection.
|
|
mBoundService.setObserver(null);
|
|
unbindService(mConnection);
|
|
mIsBound = false;
|
|
Timber.d("UNBOUND");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
Timber.d("onPause()");
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
Timber.d("onResume()");
|
|
}
|
|
|
|
private PowerManager.WakeLock wl = null;
|
|
|
|
void acquireWakeLock() {
|
|
if ((wl != null) && wl.isHeld()) return;
|
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
|
|
try {
|
|
wl.acquire();
|
|
Timber.d("WakeLock acquired");
|
|
} catch (SecurityException ex) {
|
|
Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
|
|
wl = null;
|
|
}
|
|
}
|
|
|
|
public void releaseWakeLock() {
|
|
if ((wl == null) || !wl.isHeld()) return;
|
|
wl.release();
|
|
wl = null;
|
|
Timber.d("WakeLock released");
|
|
}
|
|
|
|
public void saveWallet() {
|
|
if (mIsBound) { // no point in talking to unbound service
|
|
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
|
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE);
|
|
startService(intent);
|
|
Timber.d("STORE request sent");
|
|
} else {
|
|
Timber.e("Service not bound");
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// WalletFragment.Listener
|
|
//////////////////////////////////////////
|
|
|
|
@Override
|
|
public boolean hasBoundService() {
|
|
return mBoundService != null;
|
|
}
|
|
|
|
@Override
|
|
public Wallet.ConnectionStatus getConnectionStatus() {
|
|
return mBoundService.getConnectionStatus();
|
|
}
|
|
|
|
@Override
|
|
public long getDaemonHeight() {
|
|
return mBoundService.getDaemonHeight();
|
|
}
|
|
|
|
@Override
|
|
public void onSendRequest() {
|
|
if (needVerifyIdentity) {
|
|
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
|
@Override
|
|
public void action(String walletName, String password, boolean fingerprintUsed) {
|
|
replaceFragment(new SendFragment(), null, null);
|
|
needVerifyIdentity = false;
|
|
}
|
|
});
|
|
} else {
|
|
replaceFragment(new SendFragment(), null, null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTxDetailsRequest(TransactionInfo info) {
|
|
Bundle args = new Bundle();
|
|
args.putParcelable(TxFragment.ARG_INFO, info);
|
|
replaceFragment(new TxFragment(), null, args);
|
|
}
|
|
|
|
@Override
|
|
public void forceUpdate() {
|
|
try {
|
|
onRefreshed(getWallet(), true);
|
|
} catch (IllegalStateException ex) {
|
|
Timber.e(ex.getLocalizedMessage());
|
|
}
|
|
}
|
|
|
|
///////////////////////////
|
|
// WalletService.Observer
|
|
///////////////////////////
|
|
|
|
private int numAccounts = -1;
|
|
|
|
// refresh and return true if successful
|
|
@Override
|
|
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
|
Timber.d("onRefreshed()");
|
|
if (numAccounts != wallet.getNumAccounts()) {
|
|
numAccounts = wallet.getNumAccounts();
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
updateAccountsList();
|
|
}
|
|
});
|
|
}
|
|
try {
|
|
final WalletFragment walletFragment = (WalletFragment)
|
|
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
|
if (wallet.isSynchronized()) {
|
|
Timber.d("onRefreshed() synced");
|
|
releaseWakeLock(); // the idea is to stay awake until synced
|
|
if (!synced) { // first sync
|
|
onProgress(-1);
|
|
saveWallet(); // save on first sync
|
|
synced = true;
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
walletFragment.onSynced();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
walletFragment.onRefreshed(wallet, full);
|
|
}
|
|
});
|
|
return true;
|
|
} catch (ClassCastException ex) {
|
|
// not in wallet fragment (probably send monero)
|
|
Timber.d(ex.getLocalizedMessage());
|
|
// keep calm and carry on
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onWalletStored(final boolean success) {
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
if (success) {
|
|
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show();
|
|
} else {
|
|
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
boolean haveWallet = false;
|
|
|
|
@Override
|
|
public void onWalletStarted(final boolean success) {
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
if (!success) {
|
|
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
});
|
|
if (!success) {
|
|
finish();
|
|
} else {
|
|
haveWallet = true;
|
|
invalidateOptionsMenu();
|
|
|
|
final WalletFragment walletFragment = (WalletFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
if (walletFragment != null) {
|
|
walletFragment.onLoaded();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) {
|
|
try {
|
|
final SendFragment sendFragment = (SendFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
PendingTransaction.Status status = pendingTransaction.getStatus();
|
|
if (status != PendingTransaction.Status.Status_Ok) {
|
|
String errorText = pendingTransaction.getErrorString();
|
|
getWallet().disposePendingTransaction();
|
|
sendFragment.onCreateTransactionFailed(errorText);
|
|
} else {
|
|
sendFragment.onTransactionCreated(txTag, pendingTransaction);
|
|
}
|
|
}
|
|
});
|
|
} catch (ClassCastException ex) {
|
|
// not in spend fragment
|
|
Timber.d(ex.getLocalizedMessage());
|
|
// don't need the transaction any more
|
|
getWallet().disposePendingTransaction();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSendTransactionFailed(final String error) {
|
|
try {
|
|
final SendFragment sendFragment = (SendFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
sendFragment.onSendTransactionFailed(error);
|
|
}
|
|
});
|
|
} catch (ClassCastException ex) {
|
|
// not in spend fragment
|
|
Timber.d(ex.getLocalizedMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTransactionSent(final String txId) {
|
|
try {
|
|
final SendFragment sendFragment = (SendFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
sendFragment.onTransactionSent(txId);
|
|
}
|
|
});
|
|
} catch (ClassCastException ex) {
|
|
// not in spend fragment
|
|
Timber.d(ex.getLocalizedMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSetNotes(final boolean success) {
|
|
try {
|
|
final TxFragment txFragment = (TxFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
if (!success) {
|
|
Toast.makeText(WalletActivity.this, getString(R.string.tx_notes_set_failed), Toast.LENGTH_LONG).show();
|
|
}
|
|
txFragment.onNotesSet(success);
|
|
}
|
|
});
|
|
} catch (ClassCastException ex) {
|
|
// not in tx fragment
|
|
Timber.d(ex.getLocalizedMessage());
|
|
// never mind
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onProgress(final String text) {
|
|
try {
|
|
final WalletFragment walletFragment = (WalletFragment)
|
|
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
walletFragment.setProgress(text);
|
|
}
|
|
});
|
|
} catch (ClassCastException ex) {
|
|
// not in wallet fragment (probably send monero)
|
|
Timber.d(ex.getLocalizedMessage());
|
|
// keep calm and carry on
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onProgress(final int n) {
|
|
runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
try {
|
|
WalletFragment walletFragment = (WalletFragment)
|
|
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
|
if (walletFragment != null)
|
|
walletFragment.setProgress(n);
|
|
} catch (ClassCastException ex) {
|
|
// not in wallet fragment (probably send monero)
|
|
Timber.d(ex.getLocalizedMessage());
|
|
// keep calm and carry on
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void updateProgress() {
|
|
// TODO maybe show real state of WalletService (like "still closing previous wallet")
|
|
if (hasBoundService()) {
|
|
onProgress(mBoundService.getProgressText());
|
|
onProgress(mBoundService.getProgressValue());
|
|
}
|
|
}
|
|
|
|
///////////////////////////
|
|
// SendFragment.Listener
|
|
///////////////////////////
|
|
|
|
@Override
|
|
public void onSend(UserNotes notes) {
|
|
if (mIsBound) { // no point in talking to unbound service
|
|
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
|
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SEND);
|
|
intent.putExtra(WalletService.REQUEST_CMD_SEND_NOTES, notes.txNotes);
|
|
startService(intent);
|
|
Timber.d("SEND TX request sent");
|
|
} else {
|
|
Timber.e("Service not bound");
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onSetNote(String txId, String notes) {
|
|
if (mIsBound) { // no point in talking to unbound service
|
|
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
|
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SETNOTE);
|
|
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_TX, txId);
|
|
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_NOTES, notes);
|
|
startService(intent);
|
|
Timber.d("SET NOTE request sent");
|
|
} else {
|
|
Timber.e("Service not bound");
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onPrepareSend(final String tag, final TxData txData) {
|
|
if (mIsBound) { // no point in talking to unbound service
|
|
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
|
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_TX);
|
|
intent.putExtra(WalletService.REQUEST_CMD_TX_DATA, txData);
|
|
intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag);
|
|
startService(intent);
|
|
Timber.d("CREATE TX request sent");
|
|
} else {
|
|
Timber.e("Service not bound");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getWalletSubaddress(int accountIndex, int subaddressIndex) {
|
|
return getWallet().getSubaddress(accountIndex, subaddressIndex);
|
|
}
|
|
|
|
public String getWalletName() {
|
|
return getWallet().getName();
|
|
}
|
|
|
|
void popFragmentStack(String name) {
|
|
if (name == null) {
|
|
getSupportFragmentManager().popBackStack();
|
|
} else {
|
|
getSupportFragmentManager().popBackStack(name, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
|
}
|
|
}
|
|
|
|
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
|
|
if (extras != null) {
|
|
newFragment.setArguments(extras);
|
|
}
|
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
transaction.replace(R.id.fragment_container, newFragment);
|
|
transaction.addToBackStack(stackName);
|
|
transaction.commit();
|
|
}
|
|
|
|
private void onWalletDetails() {
|
|
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
switch (which) {
|
|
case DialogInterface.BUTTON_POSITIVE:
|
|
final Bundle extras = new Bundle();
|
|
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
|
|
|
|
if (needVerifyIdentity) {
|
|
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
|
@Override
|
|
public void action(String walletName, String password, boolean fingerprintUsed) {
|
|
replaceFragment(new GenerateReviewFragment(), null, extras);
|
|
needVerifyIdentity = false;
|
|
}
|
|
});
|
|
} else {
|
|
replaceFragment(new GenerateReviewFragment(), null, extras);
|
|
}
|
|
|
|
break;
|
|
case DialogInterface.BUTTON_NEGATIVE:
|
|
// do nothing
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setMessage(getString(R.string.details_alert_message))
|
|
.setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
|
|
.setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
|
|
.show();
|
|
}
|
|
|
|
void onShareTxInfo() {
|
|
try {
|
|
TxFragment fragment = (TxFragment)
|
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
fragment.shareTxInfo();
|
|
} catch (ClassCastException ex) {
|
|
// not in wallet fragment
|
|
Timber.e(ex.getLocalizedMessage());
|
|
// keep calm and carry on
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDisposeRequest() {
|
|
//TODO consider doing this through the WalletService to avoid concurrency issues
|
|
getWallet().disposePendingTransaction();
|
|
}
|
|
|
|
private boolean startScanFragment = false;
|
|
|
|
@Override
|
|
protected void onResumeFragments() {
|
|
super.onResumeFragments();
|
|
if (startScanFragment) {
|
|
startScanFragment();
|
|
startScanFragment = false;
|
|
}
|
|
}
|
|
|
|
private void startScanFragment() {
|
|
Bundle extras = new Bundle();
|
|
replaceFragment(new ScannerFragment(), null, extras);
|
|
}
|
|
|
|
/// QR scanner callbacks
|
|
@Override
|
|
public void onScan() {
|
|
if (Helper.getCameraPermission(this)) {
|
|
startScanFragment();
|
|
} else {
|
|
Timber.i("Waiting for permissions");
|
|
}
|
|
|
|
}
|
|
|
|
private BarcodeData scannedData = null;
|
|
|
|
@Override
|
|
public boolean onScanned(String qrCode) {
|
|
// #gurke
|
|
BarcodeData bcData = BarcodeData.fromQrCode(qrCode);
|
|
if (bcData != null) {
|
|
this.scannedData = bcData;
|
|
popFragmentStack(null);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public BarcodeData popScannedData() {
|
|
BarcodeData data = scannedData;
|
|
scannedData = null;
|
|
return data;
|
|
}
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
|
|
@NonNull int[] grantResults) {
|
|
Timber.d("onRequestPermissionsResult()");
|
|
switch (requestCode) {
|
|
case Helper.PERMISSIONS_REQUEST_CAMERA:
|
|
// If request is cancelled, the result arrays are empty.
|
|
if (grantResults.length > 0
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
startScanFragment = true;
|
|
} else {
|
|
String msg = getString(R.string.message_camera_not_permitted);
|
|
Timber.e(msg);
|
|
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onWalletReceive() {
|
|
startReceive(getWallet().getAddress());
|
|
}
|
|
|
|
void startReceive(String address) {
|
|
Timber.d("startReceive()");
|
|
Bundle b = new Bundle();
|
|
b.putString("address", address);
|
|
b.putString("name", getWalletName());
|
|
startReceiveFragment(b);
|
|
}
|
|
|
|
void startReceiveFragment(Bundle extras) {
|
|
replaceFragment(new ReceiveFragment(), null, extras);
|
|
Timber.d("ReceiveFragment placed");
|
|
}
|
|
|
|
@Override
|
|
public long getTotalFunds() {
|
|
return getWallet().getUnlockedBalance();
|
|
}
|
|
|
|
@Override
|
|
public boolean verifyWalletPassword(String password) {
|
|
String walletPassword = Helper.getWalletPassword(getApplicationContext(), getWalletName(), password);
|
|
return walletPassword != null;
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
|
drawer.closeDrawer(GravityCompat.START);
|
|
return;
|
|
}
|
|
|
|
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
|
if (fragment instanceof OnBackPressedListener) {
|
|
if (!((OnBackPressedListener) fragment).onBackPressed()) {
|
|
super.onBackPressed();
|
|
}
|
|
} else {
|
|
super.onBackPressed();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onFragmentDone() {
|
|
popFragmentStack(null);
|
|
}
|
|
|
|
@Override
|
|
public SharedPreferences getPrefs() {
|
|
return getPreferences(Context.MODE_PRIVATE);
|
|
}
|
|
|
|
private List<Integer> accountIds = new ArrayList<>();
|
|
|
|
// generate and cache unique ids for use in accounts list
|
|
private int getAccountId(int accountIndex) {
|
|
final int n = accountIds.size();
|
|
for (int i = n; i <= accountIndex; i++) {
|
|
accountIds.add(View.generateViewId());
|
|
}
|
|
return accountIds.get(accountIndex);
|
|
}
|
|
|
|
// drawer stuff
|
|
void updateAccountsList() {
|
|
final Wallet wallet = getWallet();
|
|
final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName);
|
|
tvName.setText(wallet.getName());
|
|
final TextView tvBalance = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvBalance);
|
|
tvBalance.setText(getString(R.string.accounts_balance,
|
|
Helper.getDisplayAmount(wallet.getBalanceAll(), 5)));
|
|
Menu menu = accountsView.getMenu();
|
|
menu.removeGroup(R.id.accounts_list);
|
|
final int n = wallet.getNumAccounts();
|
|
for (int i = 0; i < n; i++) {
|
|
final String label = wallet.getAccountLabel(i);
|
|
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
|
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
|
if (i == wallet.getAccountIndex())
|
|
item.setChecked(true);
|
|
}
|
|
menu.setGroupCheckable(R.id.accounts_list, true, true);
|
|
}
|
|
|
|
@Override
|
|
public void setDrawerEnabled(boolean enabled) {
|
|
Timber.d("setDrawerEnabled %b", enabled);
|
|
final int lockMode = enabled ? DrawerLayout.LOCK_MODE_UNLOCKED :
|
|
DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
|
drawer.setDrawerLockMode(lockMode);
|
|
drawerToggle.setDrawerIndicatorEnabled(enabled);
|
|
invalidateOptionsMenu(); // menu may need to be changed
|
|
}
|
|
|
|
void updateAccountName() {
|
|
setSubtitle(getWallet().getAccountLabel());
|
|
updateAccountsList();
|
|
}
|
|
|
|
public void onAccountRename() {
|
|
final LayoutInflater li = LayoutInflater.from(this);
|
|
final View promptsView = li.inflate(R.layout.prompt_rename, null);
|
|
|
|
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
|
alertDialogBuilder.setView(promptsView);
|
|
|
|
final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
|
|
final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
|
|
final Wallet wallet = getWallet();
|
|
tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel()));
|
|
|
|
// set dialog message
|
|
alertDialogBuilder
|
|
.setCancelable(false)
|
|
.setPositiveButton(getString(R.string.label_ok),
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
Helper.hideKeyboardAlways(WalletActivity.this);
|
|
String newName = etRename.getText().toString();
|
|
wallet.setAccountLabel(newName);
|
|
updateAccountName();
|
|
}
|
|
})
|
|
.setNegativeButton(getString(R.string.label_cancel),
|
|
new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
Helper.hideKeyboardAlways(WalletActivity.this);
|
|
dialog.cancel();
|
|
}
|
|
});
|
|
|
|
final AlertDialog dialog = alertDialogBuilder.create();
|
|
Helper.showKeyboard(dialog);
|
|
|
|
// accept keyboard "ok"
|
|
etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
|
Helper.hideKeyboardAlways(WalletActivity.this);
|
|
String newName = etRename.getText().toString();
|
|
dialog.cancel();
|
|
wallet.setAccountLabel(newName);
|
|
updateAccountName();
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
dialog.show();
|
|
}
|
|
|
|
@Override
|
|
public boolean onNavigationItemSelected(MenuItem item) {
|
|
final int id = item.getItemId();
|
|
switch (id) {
|
|
case R.id.account_new:
|
|
getWallet().addAccount();
|
|
int newIdx = getWallet().getNumAccounts() - 1;
|
|
getWallet().setAccountIndex(newIdx);
|
|
Toast.makeText(this,
|
|
getString(R.string.accounts_new, newIdx),
|
|
Toast.LENGTH_SHORT).show();
|
|
break;
|
|
default:
|
|
Timber.d("NavigationDrawer ID=%d", id);
|
|
int accountIdx = accountIds.indexOf(id);
|
|
if (accountIdx >= 0) {
|
|
Timber.d("found @%d", accountIdx);
|
|
getWallet().setAccountIndex(accountIdx);
|
|
}
|
|
}
|
|
forceUpdate();
|
|
drawer.closeDrawer(GravityCompat.START);
|
|
return true;
|
|
}
|
|
}
|