Merge branch 'feature/server-pow' into develop
This commit is contained in:
commit
1c5aed0f6c
@ -29,6 +29,7 @@ dependencies {
|
||||
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-security-spongy:0.2.1-SNAPSHOT'
|
||||
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
|
||||
|
||||
compile 'org.slf4j:slf4j-android:1.7.12'
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MessageListActivity"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -29,28 +29,28 @@
|
||||
<activity
|
||||
android:name=".MessageDetailActivity"
|
||||
android:label="@string/title_message_detail"
|
||||
android:parentActivityName=".MessageListActivity"
|
||||
android:parentActivityName=".MainActivity"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MessageListActivity" />
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SubscriptionDetailActivity"
|
||||
android:label="@string/title_subscription_detail"
|
||||
android:parentActivityName=".MessageListActivity"
|
||||
android:parentActivityName=".MainActivity"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MessageListActivity" />
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ComposeMessageActivity"
|
||||
android:label="Compose"
|
||||
android:parentActivityName=".MessageListActivity">
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MessageListActivity" />
|
||||
android:value=".MainActivity" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
@ -79,7 +79,7 @@
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".MessageListActivity">
|
||||
android:parentActivityName=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
|
||||
|
||||
|
@ -8,6 +8,7 @@ CREATE TABLE Message (
|
||||
sent INTEGER,
|
||||
received INTEGER,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
initial_hash BINARY(64) UNIQUE,
|
||||
|
||||
FOREIGN KEY (sender) REFERENCES Address (address),
|
||||
FOREIGN KEY (recipient) REFERENCES Address (address)
|
||||
|
@ -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);
|
@ -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
|
||||
);
|
@ -42,12 +42,16 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.Serializable;
|
||||
import java.lang.ref.WeakReference;
|
||||
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.ListSelectionListener;
|
||||
import ch.dissem.apps.abit.service.BitmessageService;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
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.Plaintext;
|
||||
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.
|
||||
* </p>
|
||||
*/
|
||||
public class MessageListActivity extends AppCompatActivity
|
||||
public class MainActivity extends AppCompatActivity
|
||||
implements ListSelectionListener<Serializable>, ActionBarListener {
|
||||
public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
|
||||
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class);
|
||||
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
|
||||
private static final int ADD_IDENTITY = 1;
|
||||
|
||||
/**
|
||||
@ -99,8 +102,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
private static ServiceConnection connection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
MessageListActivity.service = new Messenger(service);
|
||||
MessageListActivity.bound = true;
|
||||
MainActivity.service = new Messenger(service);
|
||||
MainActivity.bound = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,8 +124,10 @@ public class MessageListActivity extends AppCompatActivity
|
||||
super.onCreate(savedInstanceState);
|
||||
messageRepo = Singleton.getMessageRepository(this);
|
||||
addressRepo = Singleton.getAddressRepository(this);
|
||||
|
||||
selectedLabel = messageRepo.getLabels().get(0);
|
||||
List<Label> labels = messageRepo.getLabels();
|
||||
if (selectedLabel == null) {
|
||||
selectedLabel = labels.get(0);
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_message_list);
|
||||
|
||||
@ -130,7 +135,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
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) {
|
||||
// The detail container view will be present only in the
|
||||
@ -144,7 +150,7 @@ public class MessageListActivity extends AppCompatActivity
|
||||
listFragment.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
createDrawer(toolbar);
|
||||
createDrawer(toolbar, labels);
|
||||
|
||||
Singleton.getMessageListener(this).resetNotification();
|
||||
|
||||
@ -153,21 +159,10 @@ public class MessageListActivity extends AppCompatActivity
|
||||
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
|
||||
}
|
||||
|
||||
createSyncAccount();
|
||||
}
|
||||
|
||||
private void createSyncAccount() {
|
||||
// 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);
|
||||
if (Preferences.useTrustedNode(this)) {
|
||||
SyncAdapter.startSync(this);
|
||||
} else {
|
||||
SyncAdapter.stopSync(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<>();
|
||||
for (BitmessageAddress identity : addressRepo.getIdentities()) {
|
||||
LOG.info("Adding identity " + identity.getAddress());
|
||||
@ -216,10 +211,12 @@ public class MessageListActivity extends AppCompatActivity
|
||||
.withProfiles(profiles)
|
||||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
|
||||
@Override
|
||||
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
|
||||
public boolean onProfileChanged(View view, IProfile profile, boolean
|
||||
currentProfile) {
|
||||
if (profile.getIdentifier() == ADD_IDENTITY) {
|
||||
try {
|
||||
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY);
|
||||
Message message = Message.obtain(null, BitmessageService
|
||||
.MSG_CREATE_IDENTITY);
|
||||
message.replyTo = messenger;
|
||||
service.send(message);
|
||||
} catch (RemoteException e) {
|
||||
@ -236,14 +233,15 @@ public class MessageListActivity extends AppCompatActivity
|
||||
}
|
||||
})
|
||||
.build();
|
||||
if (profiles.size() > 0) {
|
||||
if (profiles.size() > 2) { // There's always the add and manage identity items
|
||||
accountHeader.setActiveProfile(profiles.get(0), true);
|
||||
}
|
||||
incomingHandler.updateAccountHeader(accountHeader);
|
||||
|
||||
ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
|
||||
for (Label label : messageRepo.getLabels()) {
|
||||
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label);
|
||||
for (Label label : labels) {
|
||||
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag
|
||||
(label);
|
||||
if (label.getType() == null) {
|
||||
item.withIcon(CommunityMaterial.Icon.cmd_label);
|
||||
} else {
|
||||
@ -297,17 +295,21 @@ public class MessageListActivity extends AppCompatActivity
|
||||
.withChecked(BitmessageService.isRunning())
|
||||
.withOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) {
|
||||
public void onCheckedChanged(IDrawerItem drawerItem,
|
||||
CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
if (messenger != null) {
|
||||
if (isChecked) {
|
||||
try {
|
||||
service.send(Message.obtain(null, MSG_START_NODE));
|
||||
service.send(Message.obtain(null,
|
||||
MSG_START_NODE));
|
||||
} catch (RemoteException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
service.send(Message.obtain(null, MSG_STOP_NODE));
|
||||
service.send(Message.obtain(null,
|
||||
MSG_STOP_NODE));
|
||||
} catch (RemoteException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
@ -318,7 +320,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
)
|
||||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
|
||||
@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) {
|
||||
selectedLabel = (Label) item.getTag();
|
||||
showSelectedLabel();
|
||||
@ -327,15 +330,18 @@ public class MessageListActivity extends AppCompatActivity
|
||||
Nameable<?> ni = (Nameable<?>) item;
|
||||
switch (ni.getNameRes()) {
|
||||
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());
|
||||
} else {
|
||||
((SubscriptionListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList();
|
||||
}
|
||||
|
||||
break;
|
||||
case R.string.settings:
|
||||
startActivity(new Intent(MessageListActivity.this, SettingsActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, SettingsActivity
|
||||
.class));
|
||||
break;
|
||||
case R.string.archive:
|
||||
selectedLabel = null;
|
||||
@ -353,7 +359,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
private void showSelectedLabel() {
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) {
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
|
||||
MessageListFragment) {
|
||||
((MessageListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
|
||||
} else {
|
||||
@ -381,7 +388,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
else if (item instanceof BitmessageAddress)
|
||||
fragment = new SubscriptionDetailFragment();
|
||||
else
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
|
||||
"was "
|
||||
+ item.getClass().getSimpleName());
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
@ -396,7 +404,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
else if (item instanceof BitmessageAddress)
|
||||
detailIntent = new Intent(this, SubscriptionDetailActivity.class);
|
||||
else
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
|
||||
"was "
|
||||
+ item.getClass().getSimpleName());
|
||||
|
||||
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
|
||||
@ -417,7 +426,8 @@ public class MessageListActivity extends AppCompatActivity
|
||||
@Override
|
||||
protected void 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
|
||||
@ -459,8 +469,10 @@ public class MessageListActivity extends AppCompatActivity
|
||||
.withEmail(identity.getAddress())
|
||||
.withTag(identity);
|
||||
if (accountHeader.getProfiles() != null) {
|
||||
//we know that there are 2 setting elements. set the new profile above them ;)
|
||||
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2);
|
||||
//we know that there are 2 setting elements. set the new profile
|
||||
// above them ;)
|
||||
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
|
||||
() - 2);
|
||||
} else {
|
||||
accountHeader.addProfiles(newProfile);
|
||||
}
|
@ -3,7 +3,6 @@ package ch.dissem.apps.abit;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
@ -13,7 +12,7 @@ import android.view.MenuItem;
|
||||
* An activity representing a single Message detail screen. This
|
||||
* activity is only used on handset devices. On tablet-size devices,
|
||||
* item details are presented side-by-side with a list of items
|
||||
* in a {@link MessageListActivity}.
|
||||
* in a {@link MainActivity}.
|
||||
* <p/>
|
||||
* This activity is mostly just a 'shell' activity containing nothing
|
||||
* 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
|
||||
//
|
||||
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class));
|
||||
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.util.Linkify;
|
||||
import android.text.util.Linkify.TransformFilter;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@ -19,7 +18,6 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
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.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 ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN;
|
||||
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.
|
||||
* 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}
|
||||
* on handsets.
|
||||
*/
|
||||
|
@ -1,7 +1,5 @@
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
@ -55,7 +53,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
updateList(((MessageListActivity) getActivity()).getSelectedLabel());
|
||||
doUpdateList(((MainActivity) getActivity()).getSelectedLabel());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,6 +62,10 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
|
||||
|
||||
if (!isVisible()) return;
|
||||
|
||||
doUpdateList(label);
|
||||
}
|
||||
|
||||
private void doUpdateList(Label label) {
|
||||
setListAdapter(new ArrayAdapter<Plaintext>(
|
||||
getActivity(),
|
||||
android.R.layout.simple_list_item_activated_1,
|
||||
@ -114,7 +116,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
@ -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.MSG_ADD_CONTACT;
|
||||
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 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
|
||||
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
|
||||
|
||||
final int what;
|
||||
if (subscribe.isChecked())
|
||||
what = MSG_SUBSCRIBE_AND_ADD_CONTACT;
|
||||
what = MSG_SUBSCRIBE;
|
||||
else
|
||||
what = MSG_ADD_CONTACT;
|
||||
|
||||
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void 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
|
||||
|
@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.07.15.
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
@Override
|
||||
|
@ -1,13 +1,22 @@
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
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
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
|
||||
// Load the preferences from an XML resource
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ import android.view.MenuItem;
|
||||
* An activity representing a single Subscription detail screen. This
|
||||
* activity is only used on handset devices. On tablet-size devices,
|
||||
* item details are presented side-by-side with a list of items
|
||||
* in a {@link MessageListActivity}.
|
||||
* in a {@link MainActivity}.
|
||||
* <p/>
|
||||
* This activity is mostly just a 'shell' activity containing nothing
|
||||
* 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
|
||||
//
|
||||
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class));
|
||||
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
@ -34,7 +34,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* on handsets.
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import ch.dissem.apps.abit.listener.ActionBarListener;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
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
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false);
|
||||
|
||||
return rootView;
|
||||
return inflater.inflate(R.layout.fragment_contact_list, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,25 +18,13 @@ package ch.dissem.apps.abit.listener;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
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.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
|
@ -10,7 +10,7 @@ import android.support.v7.app.NotificationCompat;
|
||||
import java.util.Timer;
|
||||
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.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.utils.Property;
|
||||
@ -64,7 +64,7 @@ public class NetworkNotification extends AbstractNotification {
|
||||
}
|
||||
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);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
notification = builder.build();
|
||||
|
@ -13,7 +13,7 @@ import android.text.style.StyleSpan;
|
||||
import java.util.LinkedList;
|
||||
|
||||
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.bitmessage.entity.Plaintext;
|
||||
|
||||
@ -38,8 +38,8 @@ public class NewMessageNotification extends AbstractNotification {
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
|
||||
.setContentInfo("Info");
|
||||
|
||||
Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
|
||||
showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext);
|
||||
Intent showMessageIntent = new Intent(ctx, MainActivity.class);
|
||||
showMessageIntent.putExtra(MainActivity.EXTRA_SHOW_MESSAGE, plaintext);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
|
||||
@ -66,8 +66,8 @@ public class NewMessageNotification extends AbstractNotification {
|
||||
}
|
||||
builder.setStyle(inboxStyle);
|
||||
|
||||
Intent intent = new Intent(ctx, MessageListActivity.class);
|
||||
intent.setAction(MessageListActivity.ACTION_SHOW_INBOX);
|
||||
Intent intent = new Intent(ctx, MainActivity.class);
|
||||
intent.setAction(MainActivity.ACTION_SHOW_INBOX);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
notification = builder.build();
|
||||
|
@ -5,7 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
|
||||
import ch.dissem.apps.abit.MessageListActivity;
|
||||
import ch.dissem.apps.abit.MainActivity;
|
||||
import ch.dissem.apps.abit.R;
|
||||
|
||||
/**
|
||||
@ -18,7 +18,7 @@ public class ProofOfWorkNotification extends AbstractNotification {
|
||||
super(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);
|
||||
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
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.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
COLUMN_SUBSCRIBED
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null, null
|
||||
);
|
||||
try {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
BitmessageAddress address;
|
||||
|
||||
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
|
||||
if (privateKeyBytes != null) {
|
||||
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
|
||||
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
|
||||
(privateKeyBytes));
|
||||
address = new BitmessageAddress(privateKey);
|
||||
} else {
|
||||
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
|
||||
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
|
||||
if (publicKeyBytes != null) {
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
|
||||
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false);
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
|
||||
.getStream(),
|
||||
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
|
||||
false);
|
||||
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
|
||||
pubkey = new V4Pubkey((V3Pubkey) pubkey);
|
||||
}
|
||||
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
|
||||
private boolean exists(BitmessageAddress address) {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null);
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0) > 0;
|
||||
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
|
||||
.getAddress() + "'", null);
|
||||
try {
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0) > 0;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
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_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) {
|
||||
LOG.error("Could not update address " + address);
|
||||
}
|
||||
|
@ -74,15 +74,20 @@ public class AndroidInventory implements Inventory {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")",
|
||||
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
|
||||
(streams) + ")",
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
List<InventoryVector> result = new LinkedList<>();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
|
||||
result.add(new InventoryVector(blob));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
|
||||
result.add(new InventoryVector(blob));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -108,15 +113,19 @@ public class AndroidInventory implements Inventory {
|
||||
"hash = X'" + vector + "'",
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
if (c.isAfterLast()) {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
c.moveToFirst();
|
||||
if (c.isAfterLast()) {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
||||
return null;
|
||||
}
|
||||
|
||||
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
|
||||
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -144,13 +153,18 @@ public class AndroidInventory implements Inventory {
|
||||
where.toString(),
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
List<ObjectMessage> result = new LinkedList<>();
|
||||
while (!c.isAfterLast()) {
|
||||
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
|
||||
blob.length));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
|
||||
"hash = X'" + object.getInventoryVector() + "'",
|
||||
null, null, null, null
|
||||
);
|
||||
return c.getCount() > 0;
|
||||
try {
|
||||
return c.getCount() > 0;
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
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_RECEIVED = "received";
|
||||
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 JT_COLUMN_MESSAGE = "message_id";
|
||||
@ -112,10 +114,14 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
null, null, null,
|
||||
LBL_COLUMN_ORDER
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
result.add(getLabel(c));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
result.add(getLabel(c));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -124,27 +130,31 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
|
||||
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
|
||||
String text;
|
||||
switch (type) {
|
||||
case INBOX:
|
||||
text = ctx.getString(R.string.inbox);
|
||||
break;
|
||||
case DRAFT:
|
||||
text = ctx.getString(R.string.draft);
|
||||
break;
|
||||
case SENT:
|
||||
text = ctx.getString(R.string.sent);
|
||||
break;
|
||||
case UNREAD:
|
||||
text = ctx.getString(R.string.unread);
|
||||
break;
|
||||
case TRASH:
|
||||
text = ctx.getString(R.string.trash);
|
||||
break;
|
||||
case BROADCAST:
|
||||
text = ctx.getString(R.string.broadcasts);
|
||||
break;
|
||||
default:
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
if (type == null) {
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
} else {
|
||||
switch (type) {
|
||||
case INBOX:
|
||||
text = ctx.getString(R.string.inbox);
|
||||
break;
|
||||
case DRAFT:
|
||||
text = ctx.getString(R.string.draft);
|
||||
break;
|
||||
case SENT:
|
||||
text = ctx.getString(R.string.sent);
|
||||
break;
|
||||
case UNREAD:
|
||||
text = ctx.getString(R.string.unread);
|
||||
break;
|
||||
case TRASH:
|
||||
text = ctx.getString(R.string.trash);
|
||||
break;
|
||||
case BROADCAST:
|
||||
text = ctx.getString(R.string.broadcasts);
|
||||
break;
|
||||
default:
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
}
|
||||
}
|
||||
Label label = new Label(
|
||||
text,
|
||||
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
public int countUnread(Label label) {
|
||||
String where;
|
||||
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 {
|
||||
where = "";
|
||||
}
|
||||
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
|
||||
null, null, null, null
|
||||
);
|
||||
return c.getColumnCount();
|
||||
try {
|
||||
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
|
||||
public List<Plaintext> findMessages(Label label) {
|
||||
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 {
|
||||
return find("id NOT IN (SELECT message_id FROM Message_Label)");
|
||||
}
|
||||
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
|
||||
@Override
|
||||
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
|
||||
@ -213,34 +244,41 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
COLUMN_STATUS
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null,
|
||||
COLUMN_RECEIVED + " DESC"
|
||||
);
|
||||
try {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null,
|
||||
COLUMN_RECEIVED + " DESC"
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
|
||||
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE)));
|
||||
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
|
||||
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
|
||||
(COLUMN_TYPE)));
|
||||
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
|
||||
ByteArrayInputStream(data));
|
||||
long id = c.getLong(c.getColumnIndex(COLUMN_ID));
|
||||
builder.id(id);
|
||||
builder.IV(new InventoryVector(iv));
|
||||
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER))));
|
||||
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT))));
|
||||
builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
|
||||
(COLUMN_SENDER))));
|
||||
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
|
||||
(COLUMN_RECIPIENT))));
|
||||
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
|
||||
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));
|
||||
result.add(builder.build());
|
||||
c.moveToNext();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
|
||||
// save from address if necessary
|
||||
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.getAlias() != null) {
|
||||
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 {
|
||||
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_SENDER, message.getFrom().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_RECEIVED, message.getReceived());
|
||||
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);
|
||||
message.setId(id);
|
||||
}
|
||||
|
||||
private void update(SQLiteDatabase db, Plaintext message) throws IOException {
|
||||
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_SENDER, message.getFrom().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_RECEIVED, message.getReceived());
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets;
|
||||
*/
|
||||
public class SqlHelper extends SQLiteOpenHelper {
|
||||
// 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";
|
||||
|
||||
protected final Context ctx;
|
||||
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onUpgrade(db, 0, 1);
|
||||
onUpgrade(db, 0, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
|
||||
executeMigration(db, "V1.0__Create_table_inventory");
|
||||
executeMigration(db, "V1.1__Create_table_address");
|
||||
executeMigration(db, "V1.2__Create_table_message");
|
||||
case 1:
|
||||
// executeMigration(db, "V2.0__Update_table_message");
|
||||
executeMigration(db, "V2.1__Create_table_POW");
|
||||
default:
|
||||
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION.
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ public class BitmessageService extends Service {
|
||||
public static final int MSG_CREATE_IDENTITY = 10;
|
||||
public static final int MSG_SUBSCRIBE = 20;
|
||||
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_BROADCAST = 31;
|
||||
public static final int MSG_START_NODE = 100;
|
||||
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
|
||||
}
|
||||
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: {
|
||||
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
|
||||
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
|
||||
|
@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service {
|
||||
service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
|
||||
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] nonce) {
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
try {
|
||||
callback.onNonceCalculated(nonce);
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
} finally {
|
||||
service.stopForeground(true);
|
||||
service.stopSelf();
|
||||
|
@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] bytes) {
|
||||
callback.onNonceCalculated(bytes);
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] bytes) {
|
||||
callback.onNonceCalculated(initialHash, bytes);
|
||||
ctx.unbindService(connection);
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,27 @@ package ch.dissem.apps.abit.service;
|
||||
|
||||
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.pow.ServerPowEngine;
|
||||
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
|
||||
import ch.dissem.apps.abit.repository.AndroidInventory;
|
||||
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.util.Constants;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
|
||||
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.
|
||||
@ -21,6 +31,8 @@ public class Singleton {
|
||||
public static final Object lock = new Object();
|
||||
private static BitmessageContext bitmessageContext;
|
||||
private static MessageListener messageListener;
|
||||
private static BitmessageAddress identity;
|
||||
private static AndroidProofOfWorkRepository powRepo;
|
||||
|
||||
public static BitmessageContext getBitmessageContext(Context context) {
|
||||
if (bitmessageContext == null) {
|
||||
@ -28,15 +40,23 @@ public class Singleton {
|
||||
if (bitmessageContext == null) {
|
||||
final Context ctx = context.getApplicationContext();
|
||||
SqlHelper sqlHelper = new SqlHelper(ctx);
|
||||
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
|
||||
bitmessageContext = new BitmessageContext.Builder()
|
||||
.proofOfWorkEngine(new ServicePowEngine(ctx))
|
||||
.security(new SpongySecurity())
|
||||
.proofOfWorkEngine(new SwitchingProofOfWorkEngine(
|
||||
ctx, Constants.PREFERENCE_SERVER_POW,
|
||||
new ServerPowEngine(ctx),
|
||||
new ServicePowEngine(ctx)
|
||||
))
|
||||
.security(new AndroidSecurity())
|
||||
.nodeRegistry(new MemoryNodeRegistry())
|
||||
.inventory(new AndroidInventory(sqlHelper))
|
||||
.addressRepo(new AndroidAddressRepository(sqlHelper))
|
||||
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
|
||||
.powRepo(powRepo)
|
||||
.networkHandler(new DefaultNetworkHandler())
|
||||
.listener(getMessageListener(ctx))
|
||||
.doNotSendPubkeyOnIdentityCreation()
|
||||
.pubkeyTTL(2 * DAY)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -62,4 +82,24 @@ public class Singleton {
|
||||
public static AddressRepository getAddressRepository(Context ctx) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import android.os.Bundle;
|
||||
* of its methods
|
||||
*/
|
||||
public class Authenticator extends AbstractAccountAuthenticator {
|
||||
public static final String ACCOUNT_NAME = "Bitmessage";
|
||||
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage";
|
||||
public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
|
||||
public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
|
||||
|
||||
// Simple constructor
|
||||
public Authenticator(Context context) {
|
||||
|
@ -1,24 +1,34 @@
|
||||
package ch.dissem.apps.abit.synchronization;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
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.util.Preferences;
|
||||
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
|
||||
@ -27,6 +37,8 @@ import ch.dissem.bitmessage.BitmessageContext;
|
||||
public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
|
||||
|
||||
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
|
||||
|
||||
private final BitmessageContext bmc;
|
||||
|
||||
/**
|
||||
@ -38,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
|
||||
@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 (bmc.isRunning()) {
|
||||
LOG.info("Synchronization skipped, Abit is acting as a full node");
|
||||
@ -46,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
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 {
|
||||
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");
|
||||
} catch (UnknownHostException e) {
|
||||
new ErrorNotification(getContext())
|
||||
.setError(R.string.error_invalid_sync_host)
|
||||
.show();
|
||||
} catch (RuntimeException 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;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,14 @@ package ch.dissem.apps.abit.util;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by chrigu on 16.11.15.
|
||||
* @author Christian Basler
|
||||
*/
|
||||
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 Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
|
||||
}
|
||||
|
341
app/src/main/java/ch/dissem/apps/abit/util/PRNGFixes.java
Normal file
341
app/src/main/java/ch/dissem/apps/abit/util/PRNGFixes.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
90
app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
Normal file
90
app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
Normal 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);
|
||||
}
|
||||
}
|
@ -1,21 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
android:textSize="10dp"
|
||||
tools:ignore="SpUsage" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/label_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
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
|
||||
android:id="@+id/label"
|
||||
@ -30,8 +37,9 @@
|
||||
android:id="@+id/subscribe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/address"
|
||||
android:layout_alignStart="@+id/address"
|
||||
android:layout_below="@+id/label_wrapper"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/subscribe" />
|
||||
|
@ -49,4 +49,6 @@
|
||||
<string name="compose_body_hint">Nachricht schreiben</string>
|
||||
<string name="contacts_and_subscriptions">Kontakte</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>
|
@ -49,4 +49,6 @@
|
||||
<string name="compose_body_hint">Write message</string>
|
||||
<string name="contacts_and_subscriptions">Contacts</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>
|
||||
|
@ -24,4 +24,11 @@
|
||||
android:key="sync_timeout"
|
||||
android:summary="@string/sync_timeout_summary"
|
||||
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>
|
@ -9,7 +9,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
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
|
||||
// in the individual module build.gradle files
|
||||
|
Loading…
Reference in New Issue
Block a user