Merge branch 'feature/server-pow' into develop

This commit is contained in:
Christian Basler 2015-12-22 17:53:22 +01:00
commit 1c5aed0f6c
41 changed files with 1250 additions and 230 deletions

View File

@ -29,6 +29,7 @@ dependencies {
compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT' compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT' compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT' compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
compile 'org.slf4j:slf4j-android:1.7.12' compile 'org.slf4j:slf4j-android:1.7.12'

View File

@ -18,7 +18,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MessageListActivity" android:name=".MainActivity"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -29,28 +29,28 @@
<activity <activity
android:name=".MessageDetailActivity" android:name=".MessageDetailActivity"
android:label="@string/title_message_detail" android:label="@string/title_message_detail"
android:parentActivityName=".MessageListActivity" android:parentActivityName=".MainActivity"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" /> android:value=".MainActivity" />
</activity> </activity>
<activity <activity
android:name=".SubscriptionDetailActivity" android:name=".SubscriptionDetailActivity"
android:label="@string/title_subscription_detail" android:label="@string/title_subscription_detail"
android:parentActivityName=".MessageListActivity" android:parentActivityName=".MainActivity"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" /> android:value=".MainActivity" />
</activity> </activity>
<activity <activity
android:name=".ComposeMessageActivity" android:name=".ComposeMessageActivity"
android:label="Compose" android:label="Compose"
android:parentActivityName=".MessageListActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MessageListActivity" /> android:value=".MainActivity" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SENDTO" /> <action android:name="android.intent.action.SENDTO" />
@ -79,7 +79,7 @@
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
android:label="@string/settings" android:label="@string/settings"
android:parentActivityName=".MessageListActivity"> android:parentActivityName=".MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />

View File

@ -8,6 +8,7 @@ CREATE TABLE Message (
sent INTEGER, sent INTEGER,
received INTEGER, received INTEGER,
status VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL,
initial_hash BINARY(64) UNIQUE,
FOREIGN KEY (sender) REFERENCES Address (address), FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) REFERENCES Address (address) FOREIGN KEY (recipient) REFERENCES Address (address)

View File

@ -0,0 +1,7 @@
-- This is done in V1.2, as SQLite doesn't support ADD CONSTRAINT and a proper migration
-- wasn't really necessary yet.
--
-- This file is here to reduce confusion regarding to the original migration files.
--ALTER TABLE Message ADD COLUMN initial_hash BINARY(64);
--ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash);

View File

@ -0,0 +1,7 @@
CREATE TABLE POW (
initial_hash BINARY(64) PRIMARY KEY,
data BLOB NOT NULL,
version BIGINT NOT NULL,
nonce_trials_per_byte BIGINT NOT NULL,
extra_bytes BIGINT NOT NULL
);

View File

@ -42,12 +42,16 @@ import org.slf4j.LoggerFactory;
import java.io.Serializable; import java.io.Serializable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import ch.dissem.apps.abit.listener.ActionBarListener; import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.listener.ListSelectionListener; import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.service.BitmessageService; import ch.dissem.apps.abit.service.BitmessageService;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.Authenticator; import ch.dissem.apps.abit.synchronization.Authenticator;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
@ -77,13 +81,12 @@ import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
* to listen for item selections. * to listen for item selections.
* </p> * </p>
*/ */
public class MessageListActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
implements ListSelectionListener<Serializable>, ActionBarListener { implements ListSelectionListener<Serializable>, ActionBarListener {
public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class); private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private static final int ADD_IDENTITY = 1; private static final int ADD_IDENTITY = 1;
/** /**
@ -99,8 +102,8 @@ public class MessageListActivity extends AppCompatActivity
private static ServiceConnection connection = new ServiceConnection() { private static ServiceConnection connection = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
MessageListActivity.service = new Messenger(service); MainActivity.service = new Messenger(service);
MessageListActivity.bound = true; MainActivity.bound = true;
} }
@Override @Override
@ -121,8 +124,10 @@ public class MessageListActivity extends AppCompatActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
messageRepo = Singleton.getMessageRepository(this); messageRepo = Singleton.getMessageRepository(this);
addressRepo = Singleton.getAddressRepository(this); addressRepo = Singleton.getAddressRepository(this);
List<Label> labels = messageRepo.getLabels();
selectedLabel = messageRepo.getLabels().get(0); if (selectedLabel == null) {
selectedLabel = labels.get(0);
}
setContentView(R.layout.activity_message_list); setContentView(R.layout.activity_message_list);
@ -130,7 +135,8 @@ public class MessageListActivity extends AppCompatActivity
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
MessageListFragment listFragment = new MessageListFragment(); MessageListFragment listFragment = new MessageListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit(); getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment)
.commit();
if (findViewById(R.id.message_detail_container) != null) { if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the // The detail container view will be present only in the
@ -144,7 +150,7 @@ public class MessageListActivity extends AppCompatActivity
listFragment.setActivateOnItemClick(true); listFragment.setActivateOnItemClick(true);
} }
createDrawer(toolbar); createDrawer(toolbar, labels);
Singleton.getMessageListener(this).resetNotification(); Singleton.getMessageListener(this).resetNotification();
@ -153,21 +159,10 @@ public class MessageListActivity extends AppCompatActivity
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE)); onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
} }
createSyncAccount(); if (Preferences.useTrustedNode(this)) {
} SyncAdapter.startSync(this);
} else {
private void createSyncAccount() { SyncAdapter.stopSync(this);
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE);
if (AccountManager.get(this).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
} }
} }
@ -185,7 +180,7 @@ public class MessageListActivity extends AppCompatActivity
} }
} }
private void createDrawer(Toolbar toolbar) { private void createDrawer(Toolbar toolbar, Collection<Label> labels) {
final ArrayList<IProfile> profiles = new ArrayList<>(); final ArrayList<IProfile> profiles = new ArrayList<>();
for (BitmessageAddress identity : addressRepo.getIdentities()) { for (BitmessageAddress identity : addressRepo.getIdentities()) {
LOG.info("Adding identity " + identity.getAddress()); LOG.info("Adding identity " + identity.getAddress());
@ -216,10 +211,12 @@ public class MessageListActivity extends AppCompatActivity
.withProfiles(profiles) .withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override @Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { public boolean onProfileChanged(View view, IProfile profile, boolean
currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) { if (profile.getIdentifier() == ADD_IDENTITY) {
try { try {
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY); Message message = Message.obtain(null, BitmessageService
.MSG_CREATE_IDENTITY);
message.replyTo = messenger; message.replyTo = messenger;
service.send(message); service.send(message);
} catch (RemoteException e) { } catch (RemoteException e) {
@ -236,14 +233,15 @@ public class MessageListActivity extends AppCompatActivity
} }
}) })
.build(); .build();
if (profiles.size() > 0) { if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true); accountHeader.setActiveProfile(profiles.get(0), true);
} }
incomingHandler.updateAccountHeader(accountHeader); incomingHandler.updateAccountHeader(accountHeader);
ArrayList<IDrawerItem> drawerItems = new ArrayList<>(); ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : messageRepo.getLabels()) { for (Label label : labels) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label); PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag
(label);
if (label.getType() == null) { if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label); item.withIcon(CommunityMaterial.Icon.cmd_label);
} else { } else {
@ -297,17 +295,21 @@ public class MessageListActivity extends AppCompatActivity
.withChecked(BitmessageService.isRunning()) .withChecked(BitmessageService.isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() { .withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(IDrawerItem drawerItem,
CompoundButton buttonView,
boolean isChecked) {
if (messenger != null) { if (messenger != null) {
if (isChecked) { if (isChecked) {
try { try {
service.send(Message.obtain(null, MSG_START_NODE)); service.send(Message.obtain(null,
MSG_START_NODE));
} catch (RemoteException e) { } catch (RemoteException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
} else { } else {
try { try {
service.send(Message.obtain(null, MSG_STOP_NODE)); service.send(Message.obtain(null,
MSG_STOP_NODE));
} catch (RemoteException e) { } catch (RemoteException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
@ -318,7 +320,8 @@ public class MessageListActivity extends AppCompatActivity
) )
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override @Override
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) { public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long
l, IDrawerItem item) {
if (item.getTag() instanceof Label) { if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag(); selectedLabel = (Label) item.getTag();
showSelectedLabel(); showSelectedLabel();
@ -327,15 +330,18 @@ public class MessageListActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item; Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) { switch (ni.getNameRes()) {
case R.string.contacts_and_subscriptions: case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) { if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof SubscriptionListFragment)) {
changeList(new SubscriptionListFragment()); changeList(new SubscriptionListFragment());
} else { } else {
((SubscriptionListFragment) getSupportFragmentManager() ((SubscriptionListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(); .findFragmentById(R.id.item_list)).updateList();
} }
break; break;
case R.string.settings: case R.string.settings:
startActivity(new Intent(MessageListActivity.this, SettingsActivity.class)); startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break; break;
case R.string.archive: case R.string.archive:
selectedLabel = null; selectedLabel = null;
@ -353,7 +359,8 @@ public class MessageListActivity extends AppCompatActivity
} }
private void showSelectedLabel() { private void showSelectedLabel() {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) { if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
MessageListFragment) {
((MessageListFragment) getSupportFragmentManager() ((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel); .findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else { } else {
@ -381,7 +388,8 @@ public class MessageListActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress) else if (item instanceof BitmessageAddress)
fragment = new SubscriptionDetailFragment(); fragment = new SubscriptionDetailFragment();
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
fragment.setArguments(arguments); fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@ -396,7 +404,8 @@ public class MessageListActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress) else if (item instanceof BitmessageAddress)
detailIntent = new Intent(this, SubscriptionDetailActivity.class); detailIntent = new Intent(this, SubscriptionDetailActivity.class);
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
@ -417,7 +426,8 @@ public class MessageListActivity extends AppCompatActivity
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE); bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
} }
@Override @Override
@ -459,8 +469,10 @@ public class MessageListActivity extends AppCompatActivity
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity); .withTag(identity);
if (accountHeader.getProfiles() != null) { if (accountHeader.getProfiles() != null) {
//we know that there are 2 setting elements. set the new profile above them ;) //we know that there are 2 setting elements. set the new profile
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2); // above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
() - 2);
} else { } else {
accountHeader.addProfiles(newProfile); accountHeader.addProfiles(newProfile);
} }

View File

@ -3,7 +3,6 @@ package ch.dissem.apps.abit;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
@ -13,7 +12,7 @@ import android.view.MenuItem;
* An activity representing a single Message detail screen. This * An activity representing a single Message detail screen. This
* activity is only used on handset devices. On tablet-size devices, * activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items * item details are presented side-by-side with a list of items
* in a {@link MessageListActivity}. * in a {@link MainActivity}.
* <p/> * <p/>
* This activity is mostly just a 'shell' activity containing nothing * This activity is mostly just a 'shell' activity containing nothing
* more than a {@link MessageDetailFragment}. * more than a {@link MessageDetailFragment}.
@ -64,7 +63,7 @@ public class MessageDetailActivity extends AppCompatActivity {
// //
// http://developer.android.com/design/patterns/navigation.html#up-vs-back // http://developer.android.com/design/patterns/navigation.html#up-vs-back
// //
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class)); NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View File

@ -5,7 +5,6 @@ import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.text.util.Linkify.TransformFilter; import android.text.util.Linkify.TransformFilter;
import android.util.Patterns;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -19,7 +18,6 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import java.util.Iterator; import java.util.Iterator;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Drawables; import ch.dissem.apps.abit.util.Drawables;
@ -28,8 +26,6 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import static android.text.util.Linkify.ALL;
import static android.text.util.Linkify.EMAIL_ADDRESSES;
import static android.text.util.Linkify.WEB_URLS; import static android.text.util.Linkify.WEB_URLS;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN; import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA; import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
@ -37,7 +33,7 @@ import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
/** /**
* A fragment representing a single Message detail screen. * A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MessageListActivity} * This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity} * in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets. * on handsets.
*/ */

View File

@ -1,7 +1,5 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
@ -55,7 +53,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
updateList(((MessageListActivity) getActivity()).getSelectedLabel()); doUpdateList(((MainActivity) getActivity()).getSelectedLabel());
} }
@Override @Override
@ -64,6 +62,10 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
if (!isVisible()) return; if (!isVisible()) return;
doUpdateList(label);
}
private void doUpdateList(Label label) {
setListAdapter(new ArrayAdapter<Plaintext>( setListAdapter(new ArrayAdapter<Plaintext>(
getActivity(), getActivity(),
android.R.layout.simple_list_item_activated_1, android.R.layout.simple_list_item_activated_1,
@ -114,7 +116,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
Intent intent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class); Intent intent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class);
intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MessageListActivity)getActivity()).getSelectedIdentity()); intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MainActivity) getActivity()).getSelectedIdentity());
startActivity(intent); startActivity(intent);
} }
}); });

View File

@ -43,7 +43,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS; import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT; import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE; import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT;
public class OpenBitmessageLinkActivity extends AppCompatActivity { public class OpenBitmessageLinkActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class); private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
final int what; final int what;
if (subscribe.isChecked()) if (subscribe.isChecked())
what = MSG_SUBSCRIBE_AND_ADD_CONTACT; what = MSG_SUBSCRIBE;
else else
what = MSG_ADD_CONTACT; what = MSG_ADD_CONTACT;
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE); bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
} }
@Override @Override

View File

@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
/** /**
* Created by chris on 14.07.15. * @author Christian Basler
*/ */
public class SettingsActivity extends AppCompatActivity { public class SettingsActivity extends AppCompatActivity {
@Override @Override

View File

@ -1,13 +1,22 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/** /**
* Created by chris on 14.07.15. * @author Christian Basler
*/ */
public class SettingsFragment extends PreferenceFragment { public class SettingsFragment
extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
} }
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case PREFERENCE_TRUSTED_NODE:
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
if (node != null) {
SyncAdapter.startSync(getActivity());
} else {
SyncAdapter.stopSync(getActivity());
}
break;
case PREFERENCE_SERVER_POW:
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
SyncAdapter.startPowSync(getActivity());
} else {
SyncAdapter.stopPowSync(getActivity());
}
break;
}
}
} }

View File

@ -28,7 +28,7 @@ import android.view.MenuItem;
* An activity representing a single Subscription detail screen. This * An activity representing a single Subscription detail screen. This
* activity is only used on handset devices. On tablet-size devices, * activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items * item details are presented side-by-side with a list of items
* in a {@link MessageListActivity}. * in a {@link MainActivity}.
* <p/> * <p/>
* This activity is mostly just a 'shell' activity containing nothing * This activity is mostly just a 'shell' activity containing nothing
* more than a {@link SubscriptionDetailFragment}. * more than a {@link SubscriptionDetailFragment}.
@ -79,7 +79,7 @@ public class SubscriptionDetailActivity extends AppCompatActivity {
// //
// http://developer.android.com/design/patterns/navigation.html#up-vs-back // http://developer.android.com/design/patterns/navigation.html#up-vs-back
// //
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class)); NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View File

@ -34,7 +34,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
/** /**
* A fragment representing a single Message detail screen. * A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MessageListActivity} * This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity} * in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets. * on handsets.
*/ */

View File

@ -16,6 +16,7 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
@ -100,12 +102,18 @@ public class SubscriptionListFragment extends AbstractItemListFragment<Bitmessag
}); });
} }
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
if (ctx instanceof ActionBarListener){
((ActionBarListener) ctx).updateTitle(getString(R.string.contacts_and_subscriptions));
}
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false); return inflater.inflate(R.layout.fragment_contact_list, container, false);
return rootView;
} }
@Override @Override

View File

@ -0,0 +1,34 @@
package ch.dissem.apps.abit.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import ch.dissem.apps.abit.util.PRNGFixes;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.security.sc.SpongySecurity;
import ch.dissem.bitmessage.utils.UnixTime;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* @author Christian Basler
*/
public class AndroidSecurity extends SpongySecurity {
public AndroidSecurity() {
PRNGFixes.apply();
}
}

View File

@ -0,0 +1,52 @@
package ch.dissem.apps.abit.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.util.Arrays;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
/**
* Switches between two {@link ProofOfWorkEngine}s depending on the configuration.
*
* @author Christian Basler
*/
public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder {
private final Context ctx;
private final String preference;
private final ProofOfWorkEngine option;
private final ProofOfWorkEngine fallback;
public SwitchingProofOfWorkEngine(Context ctx, String preference,
ProofOfWorkEngine option, ProofOfWorkEngine fallback) {
this.ctx = ctx;
this.preference = preference;
this.option = option;
this.fallback = fallback;
}
@Override
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
if (preferences.getBoolean(preference, false)) {
option.calculateNonce(initialHash, target, callback);
} else {
fallback.calculateNonce(initialHash, target, callback);
}
}
@Override
public void setContext(InternalContext context) {
for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) {
if (e instanceof InternalContext.ContextHolder) {
((InternalContext.ContextHolder) e).setContext(context);
}
}
}
}

View File

@ -18,25 +18,13 @@ package ch.dissem.apps.abit.listener;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.v7.app.NotificationCompat; import android.support.v7.app.NotificationCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.NewMessageNotification; import ch.dissem.apps.abit.notification.NewMessageNotification;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;

View File

@ -10,7 +10,7 @@ import android.support.v7.app.NotificationCompat;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import ch.dissem.apps.abit.MessageListActivity; import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.utils.Property; import ch.dissem.bitmessage.utils.Property;
@ -64,7 +64,7 @@ public class NetworkNotification extends AbstractNotification {
} }
builder.setContentText(info); builder.setContentText(info);
} }
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); Intent showMessageIntent = new Intent(ctx, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0);
builder.setContentIntent(pendingIntent); builder.setContentIntent(pendingIntent);
notification = builder.build(); notification = builder.build();

View File

@ -13,7 +13,7 @@ import android.text.style.StyleSpan;
import java.util.LinkedList; import java.util.LinkedList;
import ch.dissem.apps.abit.Identicon; import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.MessageListActivity; import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
@ -38,8 +38,8 @@ public class NewMessageNotification extends AbstractNotification {
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)) .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
.setContentInfo("Info"); .setContentInfo("Info");
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); Intent showMessageIntent = new Intent(ctx, MainActivity.class);
showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext); showMessageIntent.putExtra(MainActivity.EXTRA_SHOW_MESSAGE, plaintext);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent); builder.setContentIntent(pendingIntent);
@ -66,8 +66,8 @@ public class NewMessageNotification extends AbstractNotification {
} }
builder.setStyle(inboxStyle); builder.setStyle(inboxStyle);
Intent intent = new Intent(ctx, MessageListActivity.class); Intent intent = new Intent(ctx, MainActivity.class);
intent.setAction(MessageListActivity.ACTION_SHOW_INBOX); intent.setAction(MainActivity.ACTION_SHOW_INBOX);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0);
builder.setContentIntent(pendingIntent); builder.setContentIntent(pendingIntent);
notification = builder.build(); notification = builder.build();

View File

@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.v7.app.NotificationCompat; import android.support.v7.app.NotificationCompat;
import ch.dissem.apps.abit.MessageListActivity; import ch.dissem.apps.abit.MainActivity;
import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.R;
/** /**
@ -18,7 +18,7 @@ public class ProofOfWorkNotification extends AbstractNotification {
super(ctx); super(ctx);
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); Intent showMessageIntent = new Intent(ctx, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

View File

@ -0,0 +1,81 @@
package ch.dissem.apps.abit.pow;
import android.content.Context;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class ServerPowEngine implements ProofOfWorkEngine, InternalContext
.ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class);
private final Context ctx;
private InternalContext context;
private final ExecutorService pool;
public ServerPowEngine(Context ctx) {
this.ctx = ctx;
pool = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
});
}
@Override
public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) {
pool.execute(new Runnable() {
@Override
public void run() {
BitmessageAddress identity = Singleton.getIdentity(ctx);
if (identity == null) throw new RuntimeException("No Identity for calculating POW");
ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash,
CALCULATE, target);
SyncAdapter.startPowSync(ctx);
try {
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>
(request);
cryptoMsg.signAndEncrypt(
identity,
security().createPublicKey(identity.getPublicDecryptionKey())
);
context.getNetworkHandler().send(
Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx),
cryptoMsg);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
});
}
@Override
public void setContext(InternalContext context) {
this.context = context;
}
}

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey; import ch.dissem.bitmessage.entity.payload.V3Pubkey;
@ -27,6 +28,7 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
COLUMN_SUBSCRIBED COLUMN_SUBSCRIBED
}; };
try {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, where,
null, null, null, null null, null, null, null
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
BitmessageAddress address; BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) { if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes)); PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
(privateKeyBytes));
address = new BitmessageAddress(privateKey); address = new BitmessageAddress(privateKey);
} else { } else {
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS))); address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY)); byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
if (publicKeyBytes != null) { if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false); .getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey); pubkey = new V4Pubkey((V3Pubkey) pubkey);
} }
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally {
c.close();
} }
return result; return result;
} }
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
private boolean exists(BitmessageAddress address) { private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null); Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
.getAddress() + "'", null);
try {
cursor.moveToFirst(); cursor.moveToFirst();
return cursor.getInt(0) > 0; return cursor.getInt(0) > 0;
} finally {
cursor.close();
}
} }
private void update(BitmessageAddress address) throws IOException { private void update(BitmessageAddress address) throws IOException {
@ -194,7 +205,8 @@ public class AndroidAddressRepository implements AddressRepository {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + "'", null); int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() +
"'", null);
if (update < 0) { if (update < 0) {
LOG.error("Could not update address " + address); LOG.error("Could not update address " + address);
} }

View File

@ -74,16 +74,21 @@ public class AndroidInventory implements Inventory {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")", (includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
(streams) + ")",
null, null, null, null null, null, null, null
); );
c.moveToFirst();
List<InventoryVector> result = new LinkedList<>(); List<InventoryVector> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob)); result.add(new InventoryVector(blob));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -108,6 +113,7 @@ public class AndroidInventory implements Inventory {
"hash = X'" + vector + "'", "hash = X'" + vector + "'",
null, null, null, null null, null, null, null
); );
try {
c.moveToFirst(); c.moveToFirst();
if (c.isAfterLast()) { if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector); LOG.info("Object requested that we don't have. IV: " + vector);
@ -117,6 +123,9 @@ public class AndroidInventory implements Inventory {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length); return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
} finally {
c.close();
}
} }
@Override @Override
@ -144,14 +153,19 @@ public class AndroidInventory implements Inventory {
where.toString(), where.toString(),
null, null, null, null null, null, null, null
); );
c.moveToFirst();
List<ObjectMessage> result = new LinkedList<>(); List<ObjectMessage> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length)); result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
blob.length));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
"hash = X'" + object.getInventoryVector() + "'", "hash = X'" + object.getInventoryVector() + "'",
null, null, null, null null, null, null, null
); );
try {
return c.getCount() > 0; return c.getCount() > 0;
} finally {
c.close();
}
} }
@Override @Override

View File

@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -58,6 +59,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private static final String COLUMN_SENT = "sent"; private static final String COLUMN_SENT = "sent";
private static final String COLUMN_RECEIVED = "received"; private static final String COLUMN_RECEIVED = "received";
private static final String COLUMN_STATUS = "status"; private static final String COLUMN_STATUS = "status";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String JOIN_TABLE_NAME = "Message_Label"; private static final String JOIN_TABLE_NAME = "Message_Label";
private static final String JT_COLUMN_MESSAGE = "message_id"; private static final String JT_COLUMN_MESSAGE = "message_id";
@ -112,11 +114,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null, null, null, null,
LBL_COLUMN_ORDER LBL_COLUMN_ORDER
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
result.add(getLabel(c)); result.add(getLabel(c));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -124,6 +130,9 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE)); String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName); Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
String text; String text;
if (type == null) {
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} else {
switch (type) { switch (type) {
case INBOX: case INBOX:
text = ctx.getString(R.string.inbox); text = ctx.getString(R.string.inbox);
@ -146,6 +155,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
default: default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL)); text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} }
}
Label label = new Label( Label label = new Label(
text, text,
type, type,
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
public int countUnread(Label label) { public int countUnread(Label label) {
String where; String where;
if (label != null) { if (label != null) {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND "; where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId()
+ ") AND ";
} else { } else {
where = ""; where = "";
} }
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))", "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
null, null, null, null null, null, null, null
); );
try {
return c.getColumnCount(); return c.getColumnCount();
} finally {
c.close();
}
}
@Override
public Plaintext getMessage(byte[] initialHash) {
List<Plaintext> results = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
switch (results.size()) {
case 0:
return null;
case 1:
return results.get(0);
default:
throw new RuntimeException("This shouldn't happen, found " + results.size() +
" messages, one or none was expected");
}
} }
@Override @Override
public List<Plaintext> findMessages(Label label) { public List<Plaintext> findMessages(Label label) {
if (label != null) { if (label != null) {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"); return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label
.getId() + ")");
} else { } else {
return find("id NOT IN (SELECT message_id FROM Message_Label)"); return find("id NOT IN (SELECT message_id FROM Message_Label)");
} }
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
@Override @Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'"); return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() +
"'");
} }
@Override @Override
@ -213,7 +244,6 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
COLUMN_STATUS COLUMN_STATUS
}; };
try {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
@ -221,26 +251,34 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null, null, null, null,
COLUMN_RECEIVED + " DESC" COLUMN_RECEIVED + " DESC"
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV)); byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE))); Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data)); (COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
ByteArrayInputStream(data));
long id = c.getLong(c.getColumnIndex(COLUMN_ID)); long id = c.getLong(c.getColumnIndex(COLUMN_ID));
builder.id(id); builder.id(id);
builder.IV(new InventoryVector(iv)); builder.IV(new InventoryVector(iv));
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER)))); builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT)))); (COLUMN_SENDER))));
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_RECIPIENT))));
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT))); builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED))); builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED)));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex(COLUMN_STATUS)))); builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex
(COLUMN_STATUS))));
builder.labels(findLabels(id)); builder.labels(findLabels(id));
result.add(builder.build()); result.add(builder.build());
c.moveToNext(); c.moveToNext();
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally {
c.close();
} }
return result; return result;
} }
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
// save from address if necessary // save from address if necessary
if (message.getId() == null) { if (message.getId() == null) {
BitmessageAddress savedAddress = bmc.getAddressRepo().getAddress(message.getFrom().getAddress()); BitmessageAddress savedAddress = bmc.getAddressRepository().getAddress(message
.getFrom().getAddress());
if (savedAddress == null || savedAddress.getPrivateKey() == null) { if (savedAddress == null || savedAddress.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) { if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias()); message.getFrom().setAlias(savedAddress.getAlias());
} }
bmc.getAddressRepo().save(message.getFrom()); bmc.getAddressRepository().save(message.getFrom());
} }
} }
@ -295,7 +334,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private void insert(SQLiteDatabase db, Plaintext message) throws IOException { private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name()); values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -303,13 +343,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent()); values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
long id = db.insertOrThrow(TABLE_NAME, null, values); long id = db.insertOrThrow(TABLE_NAME, null, values);
message.setId(id); message.setId(id);
} }
private void update(SQLiteDatabase db, Plaintext message) throws IOException { private void update(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name()); values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -317,6 +359,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent()); values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
db.update(TABLE_NAME, values, "id = " + message.getId(), null); db.update(TABLE_NAME, values, "id = " + message.getId(), null);
} }

View File

@ -0,0 +1,133 @@
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository {
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
private static final String TABLE_NAME = "POW";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String COLUMN_DATA = "data";
private static final String COLUMN_VERSION = "version";
private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte";
private static final String COLUMN_EXTRA_BYTES = "extra_bytes";
private final SqlHelper sql;
public AndroidProofOfWorkRepository(SqlHelper sql) {
this.sql = sql;
}
@Override
public Item getItem(byte[] initialHash) {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_DATA,
COLUMN_VERSION,
COLUMN_NONCE_TRIALS_PER_BYTE,
COLUMN_EXTRA_BYTES
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null, null, null, null
);
try {
c.moveToFirst();
if (!c.isAfterLast()) {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return new Item(
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
.length),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
);
}
} finally {
c.close();
}
throw new RuntimeException("Object requested that we don't have. Initial hash: " +
Strings.hex(initialHash));
}
@Override
public List<byte[]> getItems() {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_INITIAL_HASH
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
null, null, null, null, null
);
List<byte[]> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
result.add(initialHash);
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@Override
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_INITIAL_HASH, security().getInitialHash(object));
values.put(COLUMN_DATA, Encode.bytes(object));
values.put(COLUMN_VERSION, object.getVersion());
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, nonceTrialsPerByte);
values.put(COLUMN_EXTRA_BYTES, extraBytes);
db.insertOrThrow(TABLE_NAME, null, values);
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void removeObject(byte[] initialHash) {
SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null);
}
}

View File

@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets;
*/ */
public class SqlHelper extends SQLiteOpenHelper { public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version. // If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1; public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "jabit.db"; public static final String DATABASE_NAME = "jabit.db";
protected final Context ctx; protected final Context ctx;
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, 1); onUpgrade(db, 0, 2);
} }
@Override @Override
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V1.0__Create_table_inventory"); executeMigration(db, "V1.0__Create_table_inventory");
executeMigration(db, "V1.1__Create_table_address"); executeMigration(db, "V1.1__Create_table_address");
executeMigration(db, "V1.2__Create_table_message"); executeMigration(db, "V1.2__Create_table_message");
case 1:
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW");
default: default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. // Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION.
} }

View File

@ -34,7 +34,6 @@ public class BitmessageService extends Service {
public static final int MSG_CREATE_IDENTITY = 10; public static final int MSG_CREATE_IDENTITY = 10;
public static final int MSG_SUBSCRIBE = 20; public static final int MSG_SUBSCRIBE = 20;
public static final int MSG_ADD_CONTACT = 21; public static final int MSG_ADD_CONTACT = 21;
public static final int MSG_SUBSCRIBE_AND_ADD_CONTACT = 23;
public static final int MSG_SEND_MESSAGE = 30; public static final int MSG_SEND_MESSAGE = 30;
public static final int MSG_SEND_BROADCAST = 31; public static final int MSG_SEND_BROADCAST = 31;
public static final int MSG_START_NODE = 100; public static final int MSG_START_NODE = 100;
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
} }
break; break;
} }
case MSG_ADD_CONTACT: {
Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
if (data instanceof BitmessageAddress) {
bmc.addContact((BitmessageAddress) data);
}
break;
}
case MSG_SEND_MESSAGE: { case MSG_SEND_MESSAGE: {
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY); Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS); Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);

View File

@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service {
service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() { engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
@Override @Override
public void onNonceCalculated(byte[] nonce) { public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
try { try {
callback.onNonceCalculated(nonce); callback.onNonceCalculated(initialHash, nonce);
} finally { } finally {
service.stopForeground(true); service.stopForeground(true);
service.stopSelf(); service.stopSelf();

View File

@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca
} }
@Override @Override
public void onNonceCalculated(byte[] bytes) { public void onNonceCalculated(byte[] initialHash, byte[] bytes) {
callback.onNonceCalculated(bytes); callback.onNonceCalculated(initialHash, bytes);
ctx.unbindService(connection); ctx.unbindService(connection);
} }

View File

@ -2,17 +2,27 @@ package ch.dissem.apps.abit.service;
import android.content.Context; import android.content.Context;
import java.util.List;
import ch.dissem.apps.abit.adapter.AndroidSecurity;
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine;
import ch.dissem.apps.abit.listener.MessageListener; import ch.dissem.apps.abit.listener.MessageListener;
import ch.dissem.apps.abit.pow.ServerPowEngine;
import ch.dissem.apps.abit.repository.AndroidAddressRepository; import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory; import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository; import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
import ch.dissem.apps.abit.repository.SqlHelper; import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.apps.abit.util.Constants;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.security.sc.SpongySecurity; import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/** /**
* Provides singleton objects across the application. * Provides singleton objects across the application.
@ -21,6 +31,8 @@ public class Singleton {
public static final Object lock = new Object(); public static final Object lock = new Object();
private static BitmessageContext bitmessageContext; private static BitmessageContext bitmessageContext;
private static MessageListener messageListener; private static MessageListener messageListener;
private static BitmessageAddress identity;
private static AndroidProofOfWorkRepository powRepo;
public static BitmessageContext getBitmessageContext(Context context) { public static BitmessageContext getBitmessageContext(Context context) {
if (bitmessageContext == null) { if (bitmessageContext == null) {
@ -28,15 +40,23 @@ public class Singleton {
if (bitmessageContext == null) { if (bitmessageContext == null) {
final Context ctx = context.getApplicationContext(); final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx); SqlHelper sqlHelper = new SqlHelper(ctx);
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
bitmessageContext = new BitmessageContext.Builder() bitmessageContext = new BitmessageContext.Builder()
.proofOfWorkEngine(new ServicePowEngine(ctx)) .proofOfWorkEngine(new SwitchingProofOfWorkEngine(
.security(new SpongySecurity()) ctx, Constants.PREFERENCE_SERVER_POW,
new ServerPowEngine(ctx),
new ServicePowEngine(ctx)
))
.security(new AndroidSecurity())
.nodeRegistry(new MemoryNodeRegistry()) .nodeRegistry(new MemoryNodeRegistry())
.inventory(new AndroidInventory(sqlHelper)) .inventory(new AndroidInventory(sqlHelper))
.addressRepo(new AndroidAddressRepository(sqlHelper)) .addressRepo(new AndroidAddressRepository(sqlHelper))
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) .messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
.powRepo(powRepo)
.networkHandler(new DefaultNetworkHandler()) .networkHandler(new DefaultNetworkHandler())
.listener(getMessageListener(ctx)) .listener(getMessageListener(ctx))
.doNotSendPubkeyOnIdentityCreation()
.pubkeyTTL(2 * DAY)
.build(); .build();
} }
} }
@ -62,4 +82,24 @@ public class Singleton {
public static AddressRepository getAddressRepository(Context ctx) { public static AddressRepository getAddressRepository(Context ctx) {
return getBitmessageContext(ctx).addresses(); return getBitmessageContext(ctx).addresses();
} }
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {
if (powRepo == null) getBitmessageContext(ctx);
return powRepo;
}
public static BitmessageAddress getIdentity(Context ctx) {
if (identity == null) {
synchronized (Singleton.class) {
if (identity == null) {
List<BitmessageAddress> identities = getBitmessageContext(ctx).addresses()
.getIdentities();
if (identities.size() > 0) {
identity = identities.get(0);
}
}
}
}
return identity;
}
} }

View File

@ -12,8 +12,8 @@ import android.os.Bundle;
* of its methods * of its methods
*/ */
public class Authenticator extends AbstractAccountAuthenticator { public class Authenticator extends AbstractAccountAuthenticator {
public static final String ACCOUNT_NAME = "Bitmessage"; public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage"; public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
// Simple constructor // Simple constructor
public Authenticator(Context context) { public Authenticator(Context context) {

View File

@ -1,24 +1,34 @@
package ch.dissem.apps.abit.synchronization; package ch.dissem.apps.abit.synchronization;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter; import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.SyncResult; import android.content.SyncResult;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.net.InetAddress; import java.util.List;
import java.net.UnknownHostException;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC;
import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/** /**
* Sync Adapter to synchronize with the Bitmessage network - fetches * Sync Adapter to synchronize with the Bitmessage network - fetches
@ -27,6 +37,8 @@ import ch.dissem.bitmessage.BitmessageContext;
public class SyncAdapter extends AbstractThreadedSyncAdapter { public class SyncAdapter extends AbstractThreadedSyncAdapter {
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class); private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private final BitmessageContext bmc; private final BitmessageContext bmc;
/** /**
@ -38,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
} }
@Override @Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
if (account.equals(Authenticator.ACCOUNT_SYNC))
syncData();
else if (account.equals(Authenticator.ACCOUNT_POW))
syncPOW();
else
throw new RuntimeException("Unknown " + account);
}
private void syncData() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary // If the Bitmessage context acts as a full node, synchronization isn't necessary
if (bmc.isRunning()) { if (bmc.isRunning()) {
LOG.info("Synchronization skipped, Abit is acting as a full node"); LOG.info("Synchronization skipped, Abit is acting as a full node");
@ -46,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
} }
LOG.info("Synchronizing Bitmessage"); LOG.info("Synchronizing Bitmessage");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String trustedNode = preferences.getString("trusted_node", null);
if (trustedNode == null) return;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return;
int port;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
trustedNode = trustedNode.substring(0, index);
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_port, portString)
.show();
return;
}
} else {
port = 8444;
}
long timeoutInSeconds = Long.parseLong(preferences.getString("sync_timeout", "120"));
try { try {
LOG.info("Synchronization started"); LOG.info("Synchronization started");
bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true); bmc.synchronize(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
Preferences.getTimeoutInSeconds(getContext()),
true);
LOG.info("Synchronization finished"); LOG.info("Synchronization finished");
} catch (UnknownHostException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_host)
.show();
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
} }
private void syncPOW() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
LOG.info("Looking for completed POW");
try {
BitmessageAddress identity = Singleton.getIdentity(getContext());
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
byte[] signingKey = security().createPublicKey(identity.getPublicDecryptionKey());
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext());
List<byte[]> items = powRepo.getItems();
for (byte[] initialHash : items) {
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
byte[] target = security().getProofOfWorkTarget(item.object, item
.nonceTrialsPerByte, item.extraBytes);
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
cryptoMsg.signAndEncrypt(identity, signingKey);
CustomMessage response = bmc.send(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
cryptoMsg
);
if (response.isError()) {
LOG.error("Server responded with error: " + new String(response.getData(),
"UTF-8"));
} else {
ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read(
response, reader).decrypt(privateKey);
if (decryptedResponse.getRequest() == COMPLETE) {
bmc.internals().getProofOfWorkService().onNonceCalculated(
initialHash, decryptedResponse.getData());
}
}
}
if (items.size() == 0) {
stopPowSync(getContext());
}
LOG.info("Synchronization finished");
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
public static void startSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
public static void startPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
private static Account addAccount(Context ctx, Account account) {
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
}
return account;
}
} }

View File

@ -3,9 +3,14 @@ package ch.dissem.apps.abit.util;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Created by chrigu on 16.11.15. * @author Christian Basler
*/ */
public class Constants { public class Constants {
public static final String PREFERENCE_WIFI_ONLY = "wifi_only";
public static final String PREFERENCE_TRUSTED_NODE = "trusted_node";
public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout";
public static final String PREFERENCE_SERVER_POW = "server_pow";
public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:"; public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:";
public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b"); public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
} }

View File

@ -0,0 +1,341 @@
package ch.dissem.apps.abit.util;
/*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will Google be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, as long as the origin is not misrepresented.
*/
import android.os.Build;
import android.os.Process;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
/**
* Fixes for the output of the default PRNG having low entropy.
* <p/>
* The fixes need to be applied via {@link #apply()} before any use of Java
* Cryptography Architecture primitives. A good place to invoke them is in the
* application's {@code onCreate}.
*
* @see <a href="http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html">
* http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html</a>
*/
public final class PRNGFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial();
/**
* Hidden constructor to prevent instantiation.
*/
private PRNGFixes() {
}
/**
* Applies all fixes.
*
* @throws SecurityException if a fix is needed but could not be applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
* fix is not needed.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException(
"Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
/**
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
* default. Does nothing if the implementation is already the default or if
* there is not need to install the implementation.
*
* @throws SecurityException if the fix is needed but could not be applied.
*/
private static void installLinuxPRNGSecureRandom()
throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class.equals(
secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng1.getProvider().getClass())) {
throw new SecurityException(
"new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
SecureRandom rng2;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new SecurityException("SHA1PRNG not available", e);
}
if (!LinuxPRNGSecureRandomProvider.class.equals(
rng2.getProvider().getClass())) {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass());
}
}
/**
* {@code Provider} of {@code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG",
1.0,
"A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output
// may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
* ({@code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
* are passed through to the Linux PRNG (/dev/urandom). Instances of
* this class seed themselves by mixing in the current time, PID, UID,
* build fingerprint, and hardware serial number (where available) into
* Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {@code null} if not yet
* opened.
*
* @GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed because
* each instance needs to seed itself if the client does not explicitly
* seed it.
*/
private boolean mSeeded;
@Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not writable.
// Log and ignore.
Log.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
}
}
@Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e);
}
}
@Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream between
// DataInputStream and FileInputStream if you need higher
// PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* @return serial number or {@code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because Build.SERIAL is only available
// since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}

View File

@ -0,0 +1,90 @@
package ch.dissem.apps.abit.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/**
* Created by chrig on 01.12.2015.
*/
public class Preferences {
private static Logger LOG = LoggerFactory.getLogger(Preferences.class);
public static boolean useTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
return trustedNode == null || trustedNode.trim().isEmpty();
}
/**
* Warning, this method might do a network call and therefore can't be called from
* the UI thread.
*/
public static InetAddress getTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return null;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return null;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
trustedNode = trustedNode.substring(0, index);
}
try {
return InetAddress.getByName(trustedNode);
} catch (UnknownHostException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_host)
.show();
LOG.error(e.getMessage(), e);
return null;
}
}
public static int getTrustedNodePort(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return 8444;
trustedNode = trustedNode.trim();
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
try {
return Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_port, portString)
.show();
}
}
return 8444;
}
public static long getTimeoutInSeconds(Context ctx) {
String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT);
return preference == null ? 120 : Long.parseLong(preference);
}
public static boolean isServerPOW(Context ctx) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getBoolean(PREFERENCE_SERVER_POW, false);
}
private static String getPreference(Context ctx, String name) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getString(name, null);
}
}

View File

@ -1,21 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp"> android:padding="24dp">
<TextView <TextView
android:id="@+id/address" android:id="@+id/address"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textSize="10dp"
tools:ignore="SpUsage" />
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/label_wrapper" android:id="@+id/label_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/address"> android:layout_alignLeft="@+id/address"
android:layout_alignStart="@+id/address"
android:layout_below="@+id/address"
android:layout_marginTop="16dp">
<EditText <EditText
android:id="@+id/label" android:id="@+id/label"
@ -30,8 +37,9 @@
android:id="@+id/subscribe" android:id="@+id/subscribe"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignLeft="@+id/address"
android:layout_alignStart="@+id/address"
android:layout_below="@+id/label_wrapper" android:layout_below="@+id/label_wrapper"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="@string/subscribe" /> android:text="@string/subscribe" />

View File

@ -49,4 +49,6 @@
<string name="compose_body_hint">Nachricht schreiben</string> <string name="compose_body_hint">Nachricht schreiben</string>
<string name="contacts_and_subscriptions">Kontakte</string> <string name="contacts_and_subscriptions">Kontakte</string>
<string name="subscribed">Abonniert</string> <string name="subscribed">Abonniert</string>
<string name="server_pow">Server POW</string>
<string name="server_pow_summary">Der vertrauenswürdige Knoten macht den Proof of Work</string>
</resources> </resources>

View File

@ -49,4 +49,6 @@
<string name="compose_body_hint">Write message</string> <string name="compose_body_hint">Write message</string>
<string name="contacts_and_subscriptions">Contacts</string> <string name="contacts_and_subscriptions">Contacts</string>
<string name="subscribed">Subscribed</string> <string name="subscribed">Subscribed</string>
<string name="server_pow">Server POW</string>
<string name="server_pow_summary">Trusted node does proof of work</string>
</resources> </resources>

View File

@ -24,4 +24,11 @@
android:key="sync_timeout" android:key="sync_timeout"
android:summary="@string/sync_timeout_summary" android:summary="@string/sync_timeout_summary"
android:title="@string/sync_timeout" /> android:title="@string/sync_timeout" />
<SwitchPreference
android:defaultValue="false"
android:key="server_pow"
android:dependency="trusted_node"
android:title="@string/server_pow"
android:summary="@string/server_pow_summary"
/>
</PreferenceScreen> </PreferenceScreen>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.5.0-beta1' classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files