From c74594f68d22a469a36a244ad28aab11d7ba4713 Mon Sep 17 00:00:00 2001 From: Adrian Tiberius Date: Wed, 12 Aug 2015 06:01:50 +0200 Subject: [PATCH] #16 - added Dapp list remembering and implemented dapps dialogs. Partially implemented json wallet import. --- .../java/io/syng/activity/BaseActivity.java | 178 +++++++++++++----- .../io/syng/adapter/AccountDrawerAdapter.java | 1 + .../java/io/syng/app/SyngApplication.java | 21 ++- app/src/main/java/io/syng/entity/Dapp.java | 22 ++- app/src/main/java/io/syng/entity/Profile.java | 66 ++++++- app/src/main/java/io/syng/util/PrefsUtil.java | 33 +++- .../main/res/layout/drawer_bottom_part.xml | 29 +++ ...et_creation_mode.xml => wallet_import.xml} | 27 +-- app/src/main/res/values/strings.xml | 11 +- 9 files changed, 319 insertions(+), 69 deletions(-) rename app/src/main/res/layout/{wallet_creation_mode.xml => wallet_import.xml} (58%) diff --git a/app/src/main/java/io/syng/activity/BaseActivity.java b/app/src/main/java/io/syng/activity/BaseActivity.java index f23bc89..27982b4 100644 --- a/app/src/main/java/io/syng/activity/BaseActivity.java +++ b/app/src/main/java/io/syng/activity/BaseActivity.java @@ -28,6 +28,7 @@ import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -37,8 +38,15 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.Glide; import org.ethereum.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; +import java.io.File; +import java.io.FileInputStream; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -61,16 +69,14 @@ import static org.ethereum.config.SystemProperties.CONFIG; public abstract class BaseActivity extends AppCompatActivity implements OnClickListener, OnDAppClickListener, OnProfileClickListener { + private static final Logger logger = LoggerFactory.getLogger("SyngApplication"); + private static final int DRAWER_CLOSE_DELAY_SHORT = 200; private static final int DRAWER_CLOSE_DELAY_LONG = 400; private static final String CONTRIBUTE_LINK = "https://github.com/syng-io"; private static final String CONTINUE_SEARCH_LINK = "dapp://syng.io/store?q=search%20query"; - private ArrayList mDAppNamesList = new ArrayList<>(Arrays.asList("Console")); - - private ArrayList mAccountNamesList = new ArrayList<>(Arrays.asList("Cow")); - private ActionBarDrawerToggle mDrawerToggle; private Spinner mAccountSpinner; @@ -108,12 +114,6 @@ public abstract class BaseActivity extends AppCompatActivity implements mDrawerLayout = (DrawerLayout) inflater.inflate(R.layout.drawer, null, false); FrameLayout content = (FrameLayout) mDrawerLayout.findViewById(R.id.content); - mDAppsRecyclerView = (RecyclerView) mDrawerLayout.findViewById(R.id.dapd_drawer_recycler_view); - mDAppsRecyclerView.setHasFixedSize(true); - RecyclerView.LayoutManager layoutManager1 = new LinearLayoutManager(this); - mDAppsRecyclerView.setLayoutManager(layoutManager1); - initDApps(); - Toolbar toolbar = (Toolbar) inflater.inflate(layoutResID, content, true).findViewById(R.id.myToolbar); if (toolbar != null) { setSupportActionBar(toolbar); @@ -137,6 +137,7 @@ public abstract class BaseActivity extends AppCompatActivity implements mSearchTextView = (EditText) mDrawerLayout.findViewById(R.id.search); initSearch(); + mDrawerLayout.findViewById(R.id.ll_import_wallet).setOnClickListener(this); mDrawerLayout.findViewById(R.id.ll_settings).setOnClickListener(this); mDrawerLayout.findViewById(R.id.ll_contribute).setOnClickListener(this); mDrawerLayout.findViewById(R.id.drawer_header).setOnClickListener(this); @@ -150,6 +151,12 @@ public abstract class BaseActivity extends AppCompatActivity implements mAccountsRecyclerView.setLayoutManager(layoutManager2); initProfiles(); + mDAppsRecyclerView = (RecyclerView) mDrawerLayout.findViewById(R.id.dapd_drawer_recycler_view); + mDAppsRecyclerView.setHasFixedSize(true); + RecyclerView.LayoutManager layoutManager1 = new LinearLayoutManager(this); + mDAppsRecyclerView.setLayoutManager(layoutManager1); + initDApps(); + ImageView header = (ImageView) mDrawerLayout.findViewById(R.id.iv_header); Glide.with(this).load(R.drawable.two).into(header); @@ -180,39 +187,40 @@ public abstract class BaseActivity extends AppCompatActivity implements private void initProfiles() { - mProfiles = new ArrayList<>(mAccountNamesList.size()); - for (String name : mAccountNamesList) { + mProfiles = PrefsUtil.getProfiles(); + // Add default cow account if not present + if (mProfiles.size() == 0) { Profile profile = new Profile(); - profile.setName(name); - // default cow account - if (name.equals("Cow")) { - // Add default cow and monkey addresses - List addresses = new ArrayList(); - byte[] cowAddr = HashUtil.sha3("cow".getBytes()); - addresses.add(Hex.toHexString(cowAddr)); - String secret = CONFIG.coinbaseSecret(); - byte[] cbAddr = HashUtil.sha3(secret.getBytes()); - addresses.add(Hex.toHexString(cbAddr)); - profile.setPrivateKeys(addresses); - SyngApplication app = (SyngApplication) getApplication(); - if (app.currentProfile == null) { - changeProfile(profile); - } - } + profile.setName("Cow"); + // Add default cow and monkey addresses + List addresses = new ArrayList(); + byte[] cowAddr = HashUtil.sha3("cow".getBytes()); + addresses.add(Hex.toHexString(cowAddr)); + String secret = CONFIG.coinbaseSecret(); + byte[] cbAddr = HashUtil.sha3(secret.getBytes()); + addresses.add(Hex.toHexString(cbAddr)); + profile.setPrivateKeys(addresses); + PrefsUtil.saveProfile(profile); mProfiles.add(profile); } mAccountDrawerAdapter = new AccountDrawerAdapter(this, mProfiles, this); mAccountsRecyclerView.setAdapter(mAccountDrawerAdapter); + if (SyngApplication.currentProfile == null) { + SyngApplication.changeProfile(mProfiles.get(0)); + } } private void initDApps() { - mDApps = new ArrayList<>(mDAppNamesList.size()); - for (String name : mDAppNamesList) { - mDApps.add(new Dapp(name)); + mDApps = new ArrayList<>(); + if (SyngApplication.currentProfile != null) { + mDApps = SyngApplication.currentProfile.getDapps(); } - mDAppsDrawerAdapter = new DAppDrawerAdapter(new ArrayList<>(mDApps), this); - mDAppsRecyclerView.setAdapter(mDAppsDrawerAdapter); + // Add default Console dapp if not present + if (mDApps.size() == 0) { + mDApps.add(new Dapp("Console")); + } + updateAppList(mSearchTextView.getText().toString()); } @@ -226,11 +234,8 @@ public abstract class BaseActivity extends AppCompatActivity implements if (textView != null) { textView.setText(profile.getName()); } - SyngApplication application = (SyngApplication) getApplication(); - List privateKeys = profile.getPrivateKeys(); - SyngApplication.sEthereumConnector.init(privateKeys); - application.currentProfile = profile; - //currentPosition = spinnerAdapter.getPosition(profile); + SyngApplication.changeProfile(profile); + initDApps(); } protected void requestChangeProfile(Profile profile) { @@ -374,6 +379,9 @@ public abstract class BaseActivity extends AppCompatActivity implements @Override public void onClick(View v) { switch (v.getId()) { + case R.id.ll_import_wallet: + onProfileImport(); + break; case R.id.ll_contribute: String url = CONTRIBUTE_LINK; Intent intent = new Intent(Intent.ACTION_VIEW); @@ -405,6 +413,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Profile profile = new Profile(); profile.setName(input.toString()); mProfiles.add(profile); + PrefsUtil.saveProfile(profile); mAccountDrawerAdapter.notifyDataSetChanged(); } }).show(); @@ -501,17 +510,57 @@ public abstract class BaseActivity extends AppCompatActivity implements } @Override - public void onDAppPress(final Dapp dapp) { + public void onProfileImport() { new MaterialDialog.Builder(this) - .title("Edit") - .customView(R.layout.dapp_form, true) - .positiveText(R.string.save) + .title(R.string.wallet_title) + .customView(R.layout.wallet_import, true) + .positiveText(R.string.sImport) .negativeText(R.string.cancel) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { - Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show(); + RadioButton importJsonRadio = (RadioButton)dialog.findViewById(R.id.radio_import_json); + EditText importPathEdit = (EditText)dialog.findViewById(R.id.wallet_import_path); + EditText walletPasswordEdit = (EditText)dialog.findViewById(R.id.wallet_password); + String importPath = importPathEdit.getText().toString(); + String password = walletPasswordEdit.getText().toString(); + String fileContents = null; + try { + File walletFile = new File(importPath); + if (walletFile.exists()) { + FileInputStream stream = new FileInputStream(walletFile); + try { + FileChannel fileChannel = stream.getChannel(); + MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); + fileContents = Charset.defaultCharset().decode(buffer).toString(); + } finally { + stream.close(); + } + } else { + Toast.makeText(BaseActivity.this, R.string.file_not_found, Toast.LENGTH_SHORT).show(); + logger.warn("Wallet file not found: " + importPath); + return; + } + } catch (Exception e) { + Toast.makeText(BaseActivity.this, R.string.error_reading_file, Toast.LENGTH_SHORT).show(); + logger.error("Error reading wallet file", e); + } + + if (importJsonRadio.isChecked()) { + if (SyngApplication.currentProfile != null) { + if (SyngApplication.currentProfile.importWallet(fileContents, password)) { + PrefsUtil.updateProfile(SyngApplication.currentProfile); + SyngApplication.changeProfile(SyngApplication.currentProfile); + } else { + Toast.makeText(BaseActivity.this, R.string.invalid_wallet_password, Toast.LENGTH_SHORT).show(); + } + } else { + logger.warn("SyngApplication.currentProfile is null ...?!"); + } + } else { + SyngApplication.currentProfile.importPrivateKey(fileContents, password); + } } @Override @@ -522,9 +571,41 @@ public abstract class BaseActivity extends AppCompatActivity implements .build().show(); } + @Override + public void onDAppPress(final Dapp dapp) { + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title("Edit") + .customView(R.layout.dapp_form, true) + .positiveText(R.string.save) + .negativeText(R.string.cancel) + .callback(new MaterialDialog.ButtonCallback() { + + @Override + public void onPositive(MaterialDialog dialog) { + EditText dappNameEdit = (EditText) dialog.findViewById(R.id.dapp_name); + EditText dappUrlEdit = (EditText) dialog.findViewById(R.id.dapp_url); + dapp.setName(dappNameEdit.getText().toString()); + dapp.setUrl(dappUrlEdit.getText().toString()); + SyngApplication.updateDapp(dapp); + initDApps(); + } + + @Override + public void onNegative(MaterialDialog dialog) { + dialog.hide(); + } + }) + .build(); + EditText dappNameEdit = (EditText) dialog.findViewById(R.id.dapp_name); + dappNameEdit.setText(dapp.getName()); + EditText dappUrlEdit = (EditText) dialog.findViewById(R.id.dapp_url); + dappUrlEdit.setText(dapp.getUrl()); + dialog.show(); + } + @Override public void onProfilePress(Profile profile) { - new MaterialDialog.Builder(this) + MaterialDialog dialog = new MaterialDialog.Builder(this) .title("Edit account") .content("Put your name to create new account") .inputType(InputType.TYPE_CLASS_TEXT) @@ -534,7 +615,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show(); } }).show(); - + dialog.getInputEditText().setText(profile.getName()); } @Override @@ -548,7 +629,12 @@ public abstract class BaseActivity extends AppCompatActivity implements @Override public void onPositive(MaterialDialog dialog) { - Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show(); + EditText dappNameEdit = (EditText)dialog.findViewById(R.id.dapp_name); + EditText dappUrlEdit = (EditText)dialog.findViewById(R.id.dapp_url); + Dapp dapp = new Dapp(dappNameEdit.getText().toString()); + dapp.setUrl(dappUrlEdit.getText().toString()); + SyngApplication.addDapp(dapp); + initDApps(); } @Override diff --git a/app/src/main/java/io/syng/adapter/AccountDrawerAdapter.java b/app/src/main/java/io/syng/adapter/AccountDrawerAdapter.java index 345222d..c18a770 100644 --- a/app/src/main/java/io/syng/adapter/AccountDrawerAdapter.java +++ b/app/src/main/java/io/syng/adapter/AccountDrawerAdapter.java @@ -27,6 +27,7 @@ public class AccountDrawerAdapter extends RecyclerView.Adapter privateKeys = profile.getPrivateKeys(); + sEthereumConnector.init(privateKeys); + currentProfile = profile; + } + + public static void addDapp(Dapp dapp) { + currentProfile.addDapp(dapp); + PrefsUtil.updateProfile(currentProfile); + } + + public static void updateDapp(Dapp dapp) { + currentProfile.updateDapp(dapp); + PrefsUtil.updateProfile(currentProfile); + } @Override public void onCreate() { diff --git a/app/src/main/java/io/syng/entity/Dapp.java b/app/src/main/java/io/syng/entity/Dapp.java index e9b5ae9..218d6fd 100644 --- a/app/src/main/java/io/syng/entity/Dapp.java +++ b/app/src/main/java/io/syng/entity/Dapp.java @@ -1,22 +1,40 @@ package io.syng.entity; -public class Dapp { +import org.ethereum.crypto.HashUtil; +import org.spongycastle.util.encoders.Hex; + +import java.io.Serializable; + +public class Dapp implements Serializable { protected String name = ""; protected String version = ""; protected String url = ""; - protected String id; + protected String id = ""; + + private static final long serialVersionUID = 1L; public Dapp(String id, String name) { + this.id = id; this.name = name; } public Dapp(String name) { + this.name = name; + this.id = generateID(); } public Dapp() { + + this.id = generateID(); + } + + protected String generateID() { + + byte[] privateKey = HashUtil.sha3(HashUtil.randomPeerId()); + return Hex.toHexString(privateKey); } public String getName() { diff --git a/app/src/main/java/io/syng/entity/Profile.java b/app/src/main/java/io/syng/entity/Profile.java index 027d6bd..3763feb 100644 --- a/app/src/main/java/io/syng/entity/Profile.java +++ b/app/src/main/java/io/syng/entity/Profile.java @@ -1,6 +1,12 @@ package io.syng.entity; +import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; +import org.ethereum.wallet.EtherSaleWallet; +import org.ethereum.wallet.EtherSaleWalletDecoder; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import java.io.Serializable; @@ -9,6 +15,8 @@ import java.util.List; public class Profile implements Serializable { + private static final Logger logger = LoggerFactory.getLogger("Profile"); + protected String name; protected List privateKeys = new ArrayList<>(); @@ -16,7 +24,7 @@ public class Profile implements Serializable { /* "password protect profile" (encrypt the private keys) */ protected boolean passwordProtectedProfile = false; - protected List dapps; + protected List dapps = new ArrayList<>(); private static final long serialVersionUID = 1L; @@ -80,6 +88,16 @@ public class Profile implements Serializable { this.dapps.add(dapp); } + public void updateDapp(Dapp dapp) { + + for (Dapp item: dapps) { + if (item.getId().equals(dapp.getId())) { + int index = dapps.indexOf(item); + dapps.set(index, dapp); + } + } + } + public void removeDapp(Dapp dapp) { this.dapps.remove(dapp); @@ -150,4 +168,50 @@ public class Profile implements Serializable { // TODO: Decrypt private key return privateKey; } + + public boolean importWallet(String jsonWallet, String password) { + + try { + JSONObject json = new JSONObject(jsonWallet); + byte[] privateKey = null; + EtherSaleWallet wallet = new EtherSaleWallet(); + if (json.has("encseed")) { + wallet.setEncseed(json.getString("encseed")); + wallet.setEthaddr(json.getString("ethaddr")); + wallet.setEmail(json.getString("email")); + wallet.setBtcaddr(json.getString("btcaddr")); + EtherSaleWalletDecoder decoder = new EtherSaleWalletDecoder(wallet); + privateKey = decoder.getPrivateKey(password); + } else if (json.has("Crypto")) { + wallet.setEncseed(json.getJSONObject("Crypto").getJSONObject("cipherparams").getString("iv") + json.getJSONObject("Crypto").getString("ciphertext")); + wallet.setEthaddr(json.getString("address")); + EtherSaleWalletDecoder decoder = new EtherSaleWalletDecoder(wallet); + privateKey = decoder.getPrivateKey(password); + } + if (privateKey == null) { + logger.warn("Invalid json wallet file."); + return false; + } + ECKey key = ECKey.fromPrivate(privateKey); + String address = Hex.toHexString(key.getAddress()); + if (address.equals(wallet.getEthaddr())) { + privateKeys.add(Hex.toHexString(privateKey)); + } else { + logger.warn("Invalid wallet password."); + return false; + } + } catch (Exception e) { + logger.error("Error importing wallet.", e); + return false; + } + + return true; + } + + public void importPrivateKey(String privateKey, String password) { + + privateKeys.add(decryptPrivateKey(privateKey, password)); + } + + } diff --git a/app/src/main/java/io/syng/util/PrefsUtil.java b/app/src/main/java/io/syng/util/PrefsUtil.java index 6903a8c..1dc5d0a 100644 --- a/app/src/main/java/io/syng/util/PrefsUtil.java +++ b/app/src/main/java/io/syng/util/PrefsUtil.java @@ -39,7 +39,7 @@ public final class PrefsUtil { } private static Editor getEditor() { - return getInstance().mPreferences.edit(); + return getPrefs().edit(); } private static SharedPreferences getPrefs() { @@ -48,11 +48,12 @@ public final class PrefsUtil { public static void saveProfiles(ArrayList profiles) { try { - getEditor().putString(PROFILES_KEY, ObjectSerializer.serialize(profiles)); + SharedPreferences.Editor editor = getEditor(); + editor.putString(PROFILES_KEY, ObjectSerializer.serialize(profiles)); + editor.apply(); } catch (Exception e) { e.printStackTrace(); } - getEditor().apply(); } public static ArrayList getProfiles() { @@ -65,6 +66,32 @@ public final class PrefsUtil { return profiles; } + public static void updateProfile(Profile profile) { + + ArrayList profiles = getProfiles(); + for (Profile item: profiles) { + if (item.getName().equals(profile.getName())) { + int index = profiles.indexOf(item); + profiles.set(index, profile); + saveProfiles(profiles); + break; + } + } + } + + public static boolean saveProfile(Profile profile) { + + ArrayList profiles = PrefsUtil.getProfiles(); + for (Profile item: profiles) { + if (item.getName().equals(profile.getName())) { + return false; + } + } + profiles.add(profile); + saveProfiles(profiles); + return true; + } + public static void setFirstLaunch(boolean isFirstLaunch) { getEditor().putBoolean(FIRST_LAUNCH_KEY, isFirstLaunch).apply(); diff --git a/app/src/main/res/layout/drawer_bottom_part.xml b/app/src/main/res/layout/drawer_bottom_part.xml index 8215506..2e8bda4 100644 --- a/app/src/main/res/layout/drawer_bottom_part.xml +++ b/app/src/main/res/layout/drawer_bottom_part.xml @@ -17,6 +17,35 @@ android:orientation="vertical" > + + + + + + + + - - - + - + android:text="@string/hex_private_key"/> + android:singleLine="true" + android:hint="File Path"/> + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6d668ff..cf2d98e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,15 +15,18 @@ Url Save Cancel + Import Manage Profiles Syng - Wallet + Import Wallet OK - Create new Wallet - Import wallet from file - Import account from string + Import json wallet + Import hex encoded private key + The specified file was not found. + Error reading specified file. + Invalid wallet or password. Profile Password This is PRE-ALPHA software and most likely contains numerous bugs. The layout, design, and functionality may change frequently. This also lacks numerous features that will be included in later versions. Using this software you agree that you hold yourself personally accountable for interacting with this software.