diff --git a/app/.gitignore b/app/.gitignore index e783827..b19c207 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,2 +1,3 @@ .externalNativeBuild build +app.iml diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index a6fa286..0000000 --- a/app/app.iml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 3c11364..1d14de8 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -302,7 +302,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobje return reinterpret_cast(wallet); } -JNIEXPORT jboolean JNICALL +JNIEXPORT jlong JNICALL Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env, jobject instance, jstring path, jstring language, jboolean isTestNet, @@ -463,7 +463,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj //TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir); -// actually a WalletManager function, but logically in Wallet +// actually a WalletManager function, but logically in onWalletSelected JNIEXPORT jboolean JNICALL Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance, jobject walletInstance) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java new file mode 100644 index 0000000..d1f846a --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -0,0 +1,407 @@ +/* + * 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.m2049r.xmrwallet; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.util.Helper; + +import java.io.File; + +public class GenerateFragment extends Fragment { + static final String TAG = "GenerateFragment"; + + EditText etWalletName; + EditText etWalletPassword; + EditText etWalletAddress; + EditText etWalletMnemonic; + LinearLayout llRestoreKeys; + EditText etWalletViewKey; + EditText etWalletSpendKey; + EditText etWalletRestoreHeight; + Button bGenerate; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.gen_fragment, container, false); + + etWalletName = (EditText) view.findViewById(R.id.etWalletName); + etWalletPassword = (EditText) view.findViewById(R.id.etWalletPassword); + etWalletMnemonic = (EditText) view.findViewById(R.id.etWalletMnemonic); + etWalletAddress = (EditText) view.findViewById(R.id.etWalletAddress); + llRestoreKeys = (LinearLayout) view.findViewById(R.id.llRestoreKeys); + etWalletViewKey = (EditText) view.findViewById(R.id.etWalletViewKey); + etWalletSpendKey = (EditText) view.findViewById(R.id.etWalletSpendKey); + etWalletRestoreHeight = (EditText) view.findViewById(R.id.etWalletRestoreHeight); + bGenerate = (Button) view.findViewById(R.id.bGenerate); + + boolean testnet = WalletManager.getInstance().isTestNet(); + etWalletMnemonic.setTextIsSelectable(testnet); + + etWalletName.requestFocus(); + Helper.showKeyboard(getActivity()); + etWalletName.addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + if (etWalletName.length() > 0) { + bGenerate.setEnabled(true); + } else { + bGenerate.setEnabled(false); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + etWalletName.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + etWalletName.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_NEXT)) { + if (etWalletName.length() > 0) { + etWalletPassword.requestFocus(); + } // otherwise ignore + return true; + } + return false; + } + }); + + etWalletPassword.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + etWalletPassword.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_NEXT)) { + if (etWalletAddress.length() > 0) { + etWalletAddress.requestFocus(); + } else { + etWalletMnemonic.requestFocus(); + } + return true; + } + return false; + } + }); + + etWalletMnemonic.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_NEXT)) { + if (etWalletMnemonic.length() == 0) { + etWalletAddress.requestFocus(); + } else if (mnemonicOk()) { + etWalletRestoreHeight.requestFocus(); + } else { + Toast.makeText(getActivity(), getString(R.string.generate_check_mnemonic), Toast.LENGTH_LONG).show(); + } + return true; + } + return false; + } + }); + etWalletMnemonic.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + etWalletMnemonic.addTextChangedListener(new + + TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + if (etWalletMnemonic.length() > 0) { + etWalletRestoreHeight.setVisibility(View.VISIBLE); + etWalletAddress.setVisibility(View.INVISIBLE); + } else { + etWalletAddress.setVisibility(View.VISIBLE); + if (etWalletAddress.length() == 0) { + etWalletRestoreHeight.setVisibility(View.INVISIBLE); + } else { + etWalletRestoreHeight.setVisibility(View.VISIBLE); + } + + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + + etWalletAddress.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_NEXT)) { + if (etWalletAddress.length() == 0) { + if (bGenerate.getVisibility() == View.VISIBLE) { + Helper.hideKeyboard(getActivity()); + generateWallet(); + } + } else if (addressOk()) { + etWalletViewKey.requestFocus(); + } else { + Toast.makeText(getActivity(), getString(R.string.generate_check_address), Toast.LENGTH_LONG).show(); + } + return true; + } + return false; + } + }); + etWalletAddress.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + etWalletAddress.addTextChangedListener(new + + TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + if (etWalletAddress.length() > 0) { + llRestoreKeys.setVisibility(View.VISIBLE); + etWalletMnemonic.setVisibility(View.INVISIBLE); + etWalletRestoreHeight.setVisibility(View.VISIBLE); + } else { + llRestoreKeys.setVisibility(View.INVISIBLE); + etWalletMnemonic.setVisibility(View.VISIBLE); + if (etWalletMnemonic.length() == 0) { + etWalletRestoreHeight.setVisibility(View.INVISIBLE); + } else { + etWalletRestoreHeight.setVisibility(View.VISIBLE); + } + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + + etWalletViewKey.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_NEXT)) { + if (viewKeyOk()) { + etWalletSpendKey.requestFocus(); + } else { + Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show(); + } + return true; + } + return false; + } + }); + etWalletViewKey.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + + etWalletSpendKey.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_NEXT)) { + if (spendKeyOk()) { + etWalletRestoreHeight.requestFocus(); + } else { + Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show(); + } + return true; + } + return false; + } + }); + etWalletSpendKey.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + Helper.showKeyboard(getActivity()); + } + }); + + etWalletRestoreHeight.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_NEXT)) { + if (bGenerate.getVisibility() == View.VISIBLE) { + Helper.hideKeyboard(getActivity()); + generateWallet(); + } + return true; + } + return false; + } + }); + + bGenerate.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + Helper.hideKeyboard(getActivity()); + generateWallet(); + } + }); + + return view; + } + + private boolean mnemonicOk() { + String seed = etWalletMnemonic.getText().toString(); + return (seed.split("\\s").length == 25); // 25 words + } + + private boolean addressOk() { + String address = etWalletAddress.getText().toString(); + return ((address.length() == 95) && ("49A".indexOf(address.charAt(0)) >= 0)); + } + + private boolean viewKeyOk() { + String viewKey = etWalletViewKey.getText().toString(); + return (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$")); + } + + private boolean spendKeyOk() { + String spendKey = etWalletSpendKey.getText().toString(); + return ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$")))); + } + + private void generateWallet() { + String name = etWalletName.getText().toString(); + if (name.length() == 0) return; + String walletPath = Helper.getWalletPath(getActivity(), name); + if (WalletManager.getInstance().walletExists(walletPath)) { + Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show(); + etWalletName.requestFocus(); + return; + } + String password = etWalletPassword.getText().toString(); + + String seed = etWalletMnemonic.getText().toString(); + String address = etWalletAddress.getText().toString(); + + long height; + try { + height = Long.parseLong(etWalletRestoreHeight.getText().toString()); + } catch (NumberFormatException ex) { + height = 0; // Keep calm and carry on! + } + + // figure out how we want to create this wallet + // A. from scratch + if ((seed.length() == 0) && (address.length() == 0)) { + bGenerate.setVisibility(View.INVISIBLE); + activityCallback.onGenerate(name, password); + } else + // B. from seed + if (mnemonicOk()) { + bGenerate.setVisibility(View.INVISIBLE); + activityCallback.onGenerate(name, password, seed, height); + } else + // C. from keys + if (addressOk() && viewKeyOk() && (spendKeyOk())) { + String viewKey = etWalletViewKey.getText().toString(); + String spendKey = etWalletSpendKey.getText().toString(); + bGenerate.setVisibility(View.INVISIBLE); + activityCallback.onGenerate(name, password, address, viewKey, spendKey, height); + } else + // D. none of the above :) + { + Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show(); + } + } + + public void walletGenerateError() { + bGenerate.setEnabled(etWalletName.length() > 0); + bGenerate.setVisibility(View.VISIBLE); + } + + GenerateFragment.Listener activityCallback; + + public interface Listener { + void onGenerate(String name, String password); + + void onGenerate(String name, String password, String seed, long height); + + void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); + + File getStorageRoot(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof GenerateFragment.Listener) { + this.activityCallback = (GenerateFragment.Listener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement Listener"); + } + } + +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java new file mode 100644 index 0000000..8089323 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -0,0 +1,115 @@ +/* + * 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.m2049r.xmrwallet; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.m2049r.xmrwallet.model.WalletManager; + +public class GenerateReviewFragment extends Fragment { + static final String TAG = "GenerateReviewFragment"; + + TextView tvWalletName; + TextView tvWalletPassword; + TextView tvWalletAddress; + TextView tvWalletMnemonic; + TextView tvWalletViewKey; + TextView tvWalletSpendKey; + Button bAccept; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.gen_review_fragment, container, false); + + tvWalletName = (TextView) view.findViewById(R.id.tvWalletName); + tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword); + tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress); + tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey); + tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey); + tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic); + + bAccept = (Button) view.findViewById(R.id.bAccept); + + boolean testnet = WalletManager.getInstance().isTestNet(); + tvWalletMnemonic.setTextIsSelectable(testnet); + + bAccept.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + acceptWallet(); + } + }); + + showDetails(); + return view; + } + + private void acceptWallet() { + String name = tvWalletName.getText().toString(); + String password = tvWalletPassword.getText().toString(); + bAccept.setEnabled(false); + activityCallback.onAccept(name, password); + } + + public void showDetails() { + Bundle b = getArguments(); + String name = b.getString("name"); + String password = b.getString("password"); + String address = b.getString("address"); + String seed = b.getString("seed"); + String view = b.getString("viewkey"); + String spend = b.getString("spendkey"); + + tvWalletName.setText(name); + tvWalletPassword.setText(password); + tvWalletAddress.setText(address); + tvWalletMnemonic.setText(seed); + tvWalletViewKey.setText(view); + if (spend.length() > 0) { // should be == 64, but spendkey is not in the API yet + tvWalletSpendKey.setText(spend); + } else { + tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly)); + } + bAccept.setEnabled(true); + } + + GenerateReviewFragment.Listener activityCallback; + + public interface Listener { + void onAccept(String name, String password); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof GenerateReviewFragment.Listener) { + this.activityCallback = (GenerateReviewFragment.Listener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement Listener"); + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 9079c03..1ddc8df 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -19,29 +19,39 @@ package com.m2049r.xmrwallet; import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; +import android.provider.MediaStore; import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; -import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.MoneroHandlerThread; import com.m2049r.xmrwallet.util.Helper; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; -public class LoginActivity extends Activity implements LoginFragment.LoginFragmentListener { +public class LoginActivity extends AppCompatActivity + implements LoginFragment.Listener, GenerateFragment.Listener, GenerateReviewFragment.Listener { static final String TAG = "LoginActivity"; static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms @@ -60,9 +70,26 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme } } - // adapted from http://www.mkyong.com/android/android-prompt-user-input-dialog-example/ @Override - public void promptPassword(final String wallet) { + public void onWalletSelected(final String walletName) { + Log.d(TAG, "selected wallet is ." + walletName + "."); + if (walletName.equals(':' + getString(R.string.generate_title))) { + startGenerateFragment(); + } else { + // now it's getting real, check if wallet exists + String walletPath = Helper.getWalletPath(this, walletName); + if (WalletManager.getInstance().walletExists(walletPath)) { + promptPassword(walletName); + } else { // this cannot really happen as we prefilter choices + Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); + } + } + } + + AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list + + void promptPassword(final String wallet) { + if (passwordDialog != null) return; // we are already asking for password Context context = LoginActivity.this; LayoutInflater li = LayoutInflater.from(context); View promptsView = li.inflate(R.layout.prompt_password, null); @@ -81,37 +108,40 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + Helper.hideKeyboardAlways(LoginActivity.this); String pass = etPassword.getText().toString(); processPasswordEntry(wallet, pass); + passwordDialog = null; } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + Helper.hideKeyboardAlways(LoginActivity.this); dialog.cancel(); + passwordDialog = null; } }); - final AlertDialog alertDialog = alertDialogBuilder.create(); - // request keyboard - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + passwordDialog = alertDialogBuilder.create(); + Helper.showKeyboard(passwordDialog); + // accept keyboard "ok" etPassword.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)) { - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + Helper.hideKeyboardAlways(LoginActivity.this); String pass = etPassword.getText().toString(); - alertDialog.cancel(); + passwordDialog.cancel(); processPasswordEntry(wallet, pass); + passwordDialog = null; return false; } return false; } }); - alertDialog.show(); + passwordDialog.show(); } private boolean checkWalletPassword(String walletName, String password) { @@ -130,7 +160,7 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme } //////////////////////////////////////// - // LoginFragment.LoginFragmentListener + // LoginFragment.Listener //////////////////////////////////////// @Override public SharedPreferences getPrefs() { @@ -142,6 +172,11 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme return Helper.getStorageRoot(getApplicationContext()); } + @Override + public void setTitle(String title) { + super.setTitle(title); + } + //////////////////////////////////////// //////////////////////////////////////// @@ -178,7 +213,7 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme } else { String msg = getString(R.string.message_strorage_not_permitted); Log.e(TAG, msg); - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); //throw new IllegalStateException(msg); } break; @@ -190,6 +225,209 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme Fragment fragment = new LoginFragment(); getFragmentManager().beginTransaction() .add(R.id.fragment_container, fragment).commit(); - Log.d(TAG, "fragment added"); + Log.d(TAG, "LoginFragment added"); + } + + void startGenerateFragment() { + replaceFragment(new GenerateFragment(), "gen", null); + Log.d(TAG, "GenerateFragment placed"); + } + + void startReviewFragment(Bundle extras) { + replaceFragment(new GenerateReviewFragment(), null, extras); + Log.d(TAG, "GenerateReviewFragment placed"); + } + + void replaceFragment(Fragment newFragment, String name, Bundle extras) { + if (extras != null) { + newFragment.setArguments(extras); + } + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment_container, newFragment); + transaction.addToBackStack(name); + transaction.commit(); + } + + ////////////////////////////////////////// + // GenerateFragment.Listener + ////////////////////////////////////////// + static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more + + public void createWallet(final String name, final String password, final WalletCreator walletCreator) { + final GenerateFragment genFragment = (GenerateFragment) + getFragmentManager().findFragmentById(R.id.fragment_container); + File newWalletFolder = new File(getStorageRoot(), ".new"); + if (!newWalletFolder.exists()) { + if (!newWalletFolder.mkdir()) { + Log.e(TAG, "Cannot create new wallet dir " + newWalletFolder.getAbsolutePath()); + genFragment.walletGenerateError(); + return; + } + } + if (!newWalletFolder.isDirectory()) { + Log.e(TAG, "New wallet dir " + newWalletFolder.getAbsolutePath() + "is not a directory"); + genFragment.walletGenerateError(); + return; + } + File cacheFile = new File(newWalletFolder, name); + cacheFile.delete(); + File keysFile = new File(newWalletFolder, name + ".keys"); + keysFile.delete(); + final File addressFile = new File(newWalletFolder, name + ".address.txt"); + addressFile.delete(); + + if (cacheFile.exists() || keysFile.exists() || addressFile.exists()) { + Log.e(TAG, "Cannot remove all old wallet files: " + cacheFile.getAbsolutePath()); + genFragment.walletGenerateError(); + ; + return; + } + + final String newWalletPath = new File(newWalletFolder, name).getAbsolutePath(); + new Thread(null, + new Runnable() { + @Override + public void run() { + Log.d(TAG, "creating wallet " + newWalletPath); + Wallet newWallet = walletCreator.createWallet(newWalletPath, password); + final String seed = newWallet.getSeed(); + final String address = newWallet.getAddress(); + final String view = newWallet.getSecretViewKey(); + final String spend = newWallet.isWatchOnly() ? "" : "not available - use seed for recovery"; + newWallet.close(); + Log.d(TAG, "Created " + address); + runOnUiThread(new Runnable() { + public void run() { + Bundle b = new Bundle(); + b.putString("name", name); + b.putString("password", password); + b.putString("seed", seed); + b.putString("address", address); + b.putString("viewkey", view); + b.putString("spendkey", spend); + startReviewFragment(b); + } + }); + } + } + , "CreateWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start(); + } + + interface WalletCreator { + Wallet createWallet(String path, String password); + } + + @Override + public void onGenerate(String name, String password) { + createWallet(name, password, + new WalletCreator() { + public Wallet createWallet(String path, String password) { + return WalletManager.getInstance() + .createWallet(path, password, MNEMONIC_LANGUAGE); + } + }); + } + + @Override + public void onGenerate(String name, String password, final String seed, final long restoreHeight) { + createWallet(name, password, + new WalletCreator() { + public Wallet createWallet(String path, String password) { + Wallet newWallet = WalletManager.getInstance().recoveryWallet(path, seed, restoreHeight); + newWallet.setPassword(password); + newWallet.store(); + return newWallet; + } + }); + } + + @Override + public void onGenerate(String name, String password, + final String address, final String viewKey, final String spendKey, final long restoreHeight) { + createWallet(name, password, + new WalletCreator() { + public Wallet createWallet(String path, String password) { + Wallet newWallet = WalletManager.getInstance() + .createWalletFromKeys(path, MNEMONIC_LANGUAGE, restoreHeight, + address, viewKey, spendKey); + newWallet.setPassword(password); + newWallet.store(); + return newWallet; + } + }); + } + + + @Override + public void onAccept(final String name, final String password) { + final File newWalletFolder = new File(getStorageRoot(), ".new"); + final File walletFolder = getStorageRoot(); + new Thread(null, + new Runnable() { + @Override + public void run() { + final String walletPath = new File(walletFolder, name).getAbsolutePath(); + final boolean rc = copyWallet(walletFolder, newWalletFolder, name) + && + (testWallet(walletPath, password) == Wallet.Status.Status_Ok); + runOnUiThread(new Runnable() { + public void run() { + if (rc) { + getFragmentManager().popBackStack("gen", + FragmentManager.POP_BACK_STACK_INCLUSIVE); + Toast.makeText(LoginActivity.this, + getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "Wallet store failed to " + walletPath); + Toast.makeText(LoginActivity.this, + getString(R.string.generate_wallet_create_failed_2), Toast.LENGTH_LONG).show(); + } + } + }); + } + } + , "AcceptWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start(); + } + + Wallet.Status testWallet(String path, String password) { + Log.d(TAG, "testing wallet " + path); + Wallet aWallet = WalletManager.getInstance().openWallet(path, password); + if (aWallet == null) return Wallet.Status.Status_Error; // does this ever happen? + Wallet.Status status = aWallet.getStatus(); + Log.d(TAG, "wallet tested " + aWallet.getStatus()); + aWallet.close(); + return status; + } + + boolean copyWallet(File dstDir, File srcDir, String name) { + boolean success = false; + try { + // TODO: the cache is corrupt if we recover (!!) + // TODO recoveryheight is ignored but not on watchonly wallet ?! - find out why + //copyFile(dstDir, srcDir, name); + copyFile(dstDir, srcDir, name + ".keys"); + copyFile(dstDir, srcDir, name + ".address.txt"); + success = true; + } catch (IOException ex) { + Log.e(TAG, "wallet copy failed: " + ex.getMessage()); + // try to rollback + new File(dstDir, name).delete(); + new File(dstDir, name + ".keys").delete(); + new File(dstDir, name + ".address.txt").delete(); + } + return success; + } + + void copyFile(File dstDir, File srcDir, String name) throws IOException { + FileChannel inChannel = new FileInputStream(new File(srcDir, name)).getChannel(); + FileChannel outChannel = new FileOutputStream(new File(dstDir, name)).getChannel(); + try { + inChannel.transferTo(0, inChannel.size(), outChannel); + } finally { + if (inChannel != null) + inChannel.close(); + if (outChannel != null) + outChannel.close(); + } } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index f925d08..50e0627 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -26,9 +26,7 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.WindowManager; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; @@ -39,44 +37,59 @@ import android.widget.Toast; import android.widget.ToggleButton; import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.util.Helper; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.List; +import java.util.Set; +import java.util.TreeSet; public class LoginFragment extends Fragment { private static final String TAG = "LoginFragment"; + public static final String WALLETNAME_PREAMBLE = "[------] "; + public static final int WALLETNAME_PREAMBLE_LENGTH = WALLETNAME_PREAMBLE.length(); + ListView listView; - List walletList = new ArrayList<>(); + Set walletList = new TreeSet<>(new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase() + .compareTo(o2.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase()); + } + }); List displayedList = new ArrayList<>(); ToggleButton tbMainNet; EditText etDaemonAddress; - LoginFragment.LoginFragmentListener activityCallback; + Listener activityCallback; // Container Activity must implement this interface - public interface LoginFragmentListener { + public interface Listener { SharedPreferences getPrefs(); File getStorageRoot(); - void promptPassword(final String wallet); + void onWalletSelected(final String wallet); + + void setTitle(String title); } @Override public void onAttach(Context context) { super.onAttach(context); - if (context instanceof LoginFragment.LoginFragmentListener) { - this.activityCallback = (LoginFragment.LoginFragmentListener) context; + if (context instanceof Listener) { + this.activityCallback = (Listener) context; } else { throw new ClassCastException(context.toString() - + " must implement WalletFragmentListener"); + + " must implement Listener"); } } @@ -96,19 +109,18 @@ public class LoginFragment extends Fragment { tbMainNet = (ToggleButton) view.findViewById(R.id.tbMainNet); etDaemonAddress = (EditText) view.findViewById(R.id.etDaemonAddress); - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + Helper.hideKeyboard(getActivity()); etDaemonAddress.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(etDaemonAddress, InputMethodManager.SHOW_IMPLICIT); + Helper.showKeyboard(getActivity()); } }); etDaemonAddress.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)) { - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + Helper.hideKeyboard(getActivity()); return false; } return false; @@ -125,6 +137,8 @@ public class LoginFragment extends Fragment { } else { setDaemon(daemonTestNet); } + activityCallback.setTitle(getString(R.string.app_name) + " " + + getString(mainnet ? R.string.connect_mainnet : R.string.connect_testnet)); filterList(); ((BaseAdapter) listView.getAdapter()).notifyDataSetChanged(); } @@ -143,44 +157,54 @@ public class LoginFragment extends Fragment { public void onItemClick(AdapterView parent, View view, int position, long id) { EditText tvDaemonAddress = (EditText) getView().findViewById(R.id.etDaemonAddress); if (tvDaemonAddress.getText().toString().length() == 0) { - Toast.makeText(getActivity(), getString(R.string.prompt_daemon_missing), Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show(); + tvDaemonAddress.requestFocus(); + Helper.showKeyboard(getActivity()); return; } String itemValue = (String) listView.getItemAtPosition(position); - if ((isMainNet() && itemValue.charAt(1) != '4') - || (!isMainNet() && itemValue.charAt(1) != '9')) { + + if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) { + Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); + return; + } + + String x = isMainNet() ? "4-" : "9A-"; + if (x.indexOf(itemValue.charAt(1)) < 0) { Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); return; } - final int preambleLength = "[123456] ".length(); - if (itemValue.length() <= (preambleLength)) { - Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show(); - return; - } if (!checkAndSetWalletDaemon(getDaemon(), !isMainNet())) { - Toast.makeText(getActivity(), getString(R.string.warn_daemon_unavailable), Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), getString(R.string.warn_daemon_unavailable), Toast.LENGTH_SHORT).show(); return; } // looking good savePrefs(false); - String wallet = itemValue.substring(preambleLength); - activityCallback.promptPassword(wallet); + String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH); + if (itemValue.charAt(1) == '-') wallet = ':' + wallet; + activityCallback.onWalletSelected(wallet); } }); + + activityCallback.setTitle(getString(R.string.app_name) + " " + + getString(isMainNet() ? R.string.connect_mainnet : R.string.connect_testnet)); + loadList(); return view; } private void filterList() { displayedList.clear(); - char x = isMainNet() ? '4' : '9'; + String x = isMainNet() ? "4" : "9A"; for (String s : walletList) { - if (s.charAt(1) == x) displayedList.add(s); + // Log.d(TAG, "filtering " + s); + if (x.indexOf(s.charAt(1)) >= 0) displayedList.add(s); } + displayedList.add(WALLETNAME_PREAMBLE + getString(R.string.generate_title)); } private void loadList() { @@ -190,7 +214,7 @@ public class LoginFragment extends Fragment { walletList.clear(); for (WalletManager.WalletInfo walletInfo : walletInfos) { - Log.d(TAG, walletInfo.address); + // Log.d(TAG, walletInfo.address); String displayAddress = walletInfo.address; if (displayAddress.length() == 95) { displayAddress = walletInfo.address.substring(0, 6); @@ -230,8 +254,8 @@ public class LoginFragment extends Fragment { SharedPreferences sharedPref = activityCallback.getPrefs(); boolean mainnet = sharedPref.getBoolean(PREF_MAINNET, false); - daemonMainNet = sharedPref.getString(PREF_DAEMON_MAINNET, "localhost:18081"); - daemonTestNet = sharedPref.getString(PREF_DAEMON_TESTNET, "localhost:28081"); + daemonMainNet = sharedPref.getString(PREF_DAEMON_MAINNET, ""); + daemonTestNet = sharedPref.getString(PREF_DAEMON_TESTNET, ""); setMainNet(mainnet); if (mainnet) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 40ef712..a0c5620 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -25,13 +25,14 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.Toast; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.service.WalletService; -public class WalletActivity extends Activity implements WalletFragment.WalletFragmentListener, +public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener, WalletService.Observer { private static final String TAG = "WalletActivity"; @@ -200,11 +201,6 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra Intent intent = new Intent(getApplicationContext(), WalletService.class); intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE); startService(intent); - runOnUiThread(new Runnable() { - public void run() { - Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show(); - } - }); Log.d(TAG, "STORE request sent"); } else { Log.e(TAG, "Service not bound"); @@ -212,7 +208,7 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra } ////////////////////////////////////////// - // WalletFragment.WalletFragmentListener + // WalletFragment.Listener ////////////////////////////////////////// @Override @@ -259,6 +255,15 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra }); } + @Override + public void onWalletStored() { + runOnUiThread(new Runnable() { + public void run() { + Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show(); + } + }); + } + @Override public void onProgress(final String text) { //Log.d(TAG, "PROGRESS: " + text); diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index 50791bd..c708b3c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -37,11 +37,13 @@ import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; +import java.text.NumberFormat; import java.util.List; public class WalletFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener { private static final String TAG = "WalletFragment"; private TransactionInfoAdapter adapter; + private NumberFormat formatter = NumberFormat.getInstance(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -171,7 +173,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O long daemonHeight = activityCallback.getDaemonHeight(); if (!wallet.isSynchronized()) { long n = daemonHeight - wallet.getBlockChainHeight(); - sync = n + " " + getString(R.string.status_remaining); + sync = formatter.format(n) + " " + getString(R.string.status_remaining); if (firstBlock == 0) { firstBlock = wallet.getBlockChainHeight(); } @@ -180,7 +182,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O if (x == 0) x = -1; onProgress(x); } else { - sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight(); + sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight()); } } String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet)); @@ -188,10 +190,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O connectionStatusView.setText(net + " " + daemonConnected.toString().substring(17)); } - WalletFragmentListener activityCallback; + Listener activityCallback; // Container Activity must implement this interface - public interface WalletFragmentListener { + public interface Listener { boolean hasBoundService(); Wallet.ConnectionStatus getConnectionStatus(); @@ -205,15 +207,11 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O @Override public void onAttach(Context context) { super.onAttach(context); - if (context instanceof WalletFragmentListener) { - this.activityCallback = (WalletFragmentListener) context; + if (context instanceof Listener) { + this.activityCallback = (Listener) context; } else { throw new ClassCastException(context.toString() - + " must implement WalletFragmentListener"); + + " must implement Listener"); } } - - private void runOnUiThread(Runnable runnable) { - if (isAdded()) getActivity().runOnUiThread(runnable); - } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index 2757877..357027b 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -57,7 +57,7 @@ public class WalletManager { private void manageWallet(String walletId, Wallet wallet) { if (getWallet(walletId) != null) { - throw new IllegalStateException("Wallet already under management!"); + throw new IllegalStateException(walletId + " already under management!"); } Log.d(TAG, "Managing " + walletId); managedWallets.put(walletId, wallet); @@ -65,7 +65,7 @@ public class WalletManager { private void unmanageWallet(String walletId) { if (getWallet(walletId) == null) { - throw new IllegalStateException("Wallet not under management!"); + throw new IllegalStateException(walletId + " not under management!"); } Log.d(TAG, "Unmanaging " + walletId); managedWallets.remove(walletId); @@ -78,6 +78,8 @@ public class WalletManager { return wallet; } + private native long createWalletJ(String path, String password, String language, boolean isTestNet); + public Wallet openWallet(String path, String password) { long walletHandle = openWalletJ(path, password, isTestNet()); Wallet wallet = new Wallet(walletHandle); @@ -85,6 +87,8 @@ public class WalletManager { return wallet; } + private native long openWalletJ(String path, String password, boolean isTestNet); + public Wallet recoveryWallet(String path, String mnemonic) { Wallet wallet = recoveryWallet(path, mnemonic, 0); manageWallet(wallet.getName(), wallet); @@ -98,12 +102,17 @@ public class WalletManager { return wallet; } - private native long createWalletJ(String path, String password, String language, boolean isTestNet); - - private native long openWalletJ(String path, String password, boolean isTestNet); - private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight); + public Wallet createWalletFromKeys(String path, String language, long restoreHeight, + String addressString, String viewKeyString, String spendKeyString) { + long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight, + addressString, viewKeyString, spendKeyString); + Wallet wallet = new Wallet(walletHandle); + manageWallet(wallet.getName(), wallet); + return wallet; + } + private native long createWalletFromKeysJ(String path, String language, boolean isTestNet, long restoreHeight, diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java index 436d08f..ce4bc3c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java @@ -111,7 +111,8 @@ public class WalletService extends Service { fullRefresh = true; } } - observer.onRefreshed(wallet, fullRefresh); + if (observer != null) + observer.onRefreshed(wallet, fullRefresh); } } } @@ -191,6 +192,8 @@ public class WalletService extends Service { void onProgress(String text); void onProgress(int n); + + void onWalletStored(); } String progressText = null; @@ -257,6 +260,7 @@ public class WalletService extends Service { Log.d(TAG, "storing wallet: " + myWallet.getName()); getWallet().store(); Log.d(TAG, "wallet stored: " + myWallet.getName()); + if (observer != null) observer.onWalletStored(); } } break; @@ -351,6 +355,7 @@ public class WalletService extends Service { if (listener == null) { Log.d(TAG, "start() loadWallet"); Wallet aWallet = loadWallet(walletName, walletPassword); + // TODO check aWallet and die gracefully if neccessary listener = new MyWalletListener(aWallet); listener.start(); showProgress(100); @@ -416,6 +421,8 @@ public class WalletService extends Service { WalletManager.getInstance().close(wallet); // TODO close() failed? wallet = null; // TODO what do we do with the progress?? + // TODO tell the activity this failed + // this crashes in MyWalletListener(Wallet aWallet) as wallet == null } } return wallet; diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index 873c89f..182daf5 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -18,10 +18,13 @@ package com.m2049r.xmrwallet.util; import android.Manifest; import android.app.Activity; +import android.app.Dialog; import android.content.Context; import android.content.pm.PackageManager; import android.os.Environment; import android.util.Log; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import com.m2049r.xmrwallet.R; @@ -82,4 +85,26 @@ public class Helper { return Environment.MEDIA_MOUNTED.equals(state); } + static public void showKeyboard(Activity act) { + InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(act.getCurrentFocus(), InputMethodManager.SHOW_IMPLICIT); + } + + static public void hideKeyboard(Activity act) { + if (act.getCurrentFocus() == null) { + act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + } else { + InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + static public void showKeyboard(Dialog dialog) { + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } + + static public void hideKeyboardAlways(Activity act) { + act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + } } diff --git a/app/src/main/res/layout/gen_fragment.xml b/app/src/main/res/layout/gen_fragment.xml new file mode 100644 index 0000000..f9e23b8 --- /dev/null +++ b/app/src/main/res/layout/gen_fragment.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + +