diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 19861c3..f0fbceb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,104 +1,133 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="ch.dissem.apps.abit">
-
-
-
-
-
+
+
+
+
+
+
+
+
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme">
+ android:name=".MessageListActivity"
+ android:label="@string/app_name">
-
+
-
+
+ android:name=".MessageDetailActivity"
+ android:label="@string/title_message_detail"
+ android:parentActivityName=".MessageListActivity"
+ tools:ignore="UnusedAttribute">
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".MessageListActivity" />
+ android:name=".SubscriptionDetailActivity"
+ android:label="@string/title_subscription_detail"
+ android:parentActivityName=".MessageListActivity"
+ tools:ignore="UnusedAttribute">
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".MessageListActivity" />
+ android:name=".ComposeMessageActivity"
+ android:label="Compose"
+ android:parentActivityName=".MessageListActivity">
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".MessageListActivity" />
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ android:name=".SettingsActivity"
+ android:label="@string/settings"
+ android:parentActivityName=".MessageListActivity">
-
+
-
+
+ android:name=".OpenBitmessageLinkActivity"
+ android:label="@string/title_activity_open_bitmessage_link"
+ android:theme="@style/Theme.AppCompat.Light.Dialog">
-
+
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java b/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java
index 65acd36..7b03a55 100644
--- a/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java
@@ -17,13 +17,13 @@
package ch.dissem.apps.abit;
import android.app.Activity;
+import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
+
import ch.dissem.apps.abit.listener.ListSelectionListener;
-import ch.dissem.apps.abit.service.Singleton;
-import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.valueobject.Label;
/**
@@ -44,7 +44,6 @@ public abstract class AbstractItemListFragment extends ListFragment {
public void onItemSelected(Object plaintext) {
}
};
- protected BitmessageContext bmc;
/**
* The fragment's current callback object, which is notified of list item
* clicks.
@@ -54,16 +53,10 @@ public abstract class AbstractItemListFragment extends ListFragment {
* The current activated item position. Only used on tablets.
*/
private int activatedPosition = ListView.INVALID_POSITION;
+ private boolean activateOnItemClick;
abstract void updateList(Label label);
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- bmc = Singleton.getBitmessageContext(getActivity());
- }
-
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -76,15 +69,26 @@ public abstract class AbstractItemListFragment extends ListFragment {
}
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
+ public void onResume() {
+ super.onResume();
+
+ // When setting CHOICE_MODE_SINGLE, ListView will automatically
+ // give items the 'activated' state when touched.
+ getListView().setChoiceMode(activateOnItemClick
+ ? ListView.CHOICE_MODE_SINGLE
+ : ListView.CHOICE_MODE_NONE);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
// Activities containing this fragment must implement its callbacks.
- if (!(activity instanceof ListSelectionListener)) {
+ if (!(context instanceof ListSelectionListener)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
- callbacks = (ListSelectionListener) activity;
+ callbacks = (ListSelectionListener) context;
}
@Override
@@ -118,11 +122,15 @@ public abstract class AbstractItemListFragment extends ListFragment {
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
- // When setting CHOICE_MODE_SINGLE, ListView will automatically
- // give items the 'activated' state when touched.
- getListView().setChoiceMode(activateOnItemClick
- ? ListView.CHOICE_MODE_SINGLE
- : ListView.CHOICE_MODE_NONE);
+ this.activateOnItemClick = activateOnItemClick;
+
+ if (isVisible()) {
+ // When setting CHOICE_MODE_SINGLE, ListView will automatically
+ // give items the 'activated' state when touched.
+ getListView().setChoiceMode(activateOnItemClick
+ ? ListView.CHOICE_MODE_SINGLE
+ : ListView.CHOICE_MODE_NONE);
+ }
}
private void setActivatedPosition(int position) {
diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java
index 4652f6b..c43ae5f 100644
--- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java
@@ -6,12 +6,15 @@ import android.support.v4.app.Fragment;
import android.view.*;
import android.widget.ImageView;
import android.widget.TextView;
+
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Drawables;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
+import ch.dissem.bitmessage.ports.MessageRepository;
+
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import java.util.Iterator;
@@ -84,7 +87,7 @@ public class MessageDetailFragment extends Fragment {
}
}
if (removed) {
- Singleton.getBitmessageContext(inflater.getContext()).messages().save(item);
+ Singleton.getMessageRepository(inflater.getContext()).save(item);
}
return rootView;
}
@@ -103,7 +106,7 @@ public class MessageDetailFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
- BitmessageContext bmc = Singleton.getBitmessageContext(getActivity());
+ MessageRepository messageRepo = Singleton.getMessageRepository(getContext());
switch (menuItem.getItemId()) {
case R.id.reply:
Intent replyIntent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class);
@@ -113,21 +116,21 @@ public class MessageDetailFragment extends Fragment {
return true;
case R.id.delete:
if (isInTrash(item)) {
- bmc.messages().remove(item);
+ messageRepo.remove(item);
} else {
item.getLabels().clear();
- item.addLabels(bmc.messages().getLabels(Label.Type.TRASH));
- bmc.messages().save(item);
+ item.addLabels(messageRepo.getLabels(Label.Type.TRASH));
+ messageRepo.save(item);
}
getActivity().onBackPressed();
return true;
case R.id.mark_unread:
- item.addLabels(bmc.messages().getLabels(Label.Type.UNREAD));
- bmc.messages().save(item);
+ item.addLabels(messageRepo.getLabels(Label.Type.UNREAD));
+ messageRepo.save(item);
return true;
case R.id.archive:
item.getLabels().clear();
- bmc.messages().save(item);
+ messageRepo.save(item);
return true;
default:
return false;
diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java b/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java
index 51de702..b8a359f 100644
--- a/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java
+++ b/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java
@@ -1,23 +1,24 @@
package ch.dissem.apps.abit;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
-
-import ch.dissem.apps.abit.listener.ActionBarListener;
-import ch.dissem.apps.abit.listener.ListSelectionListener;
-import ch.dissem.apps.abit.notification.NetworkNotification;
-import ch.dissem.apps.abit.service.Singleton;
-import ch.dissem.bitmessage.BitmessageContext;
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-import ch.dissem.bitmessage.entity.Plaintext;
-import ch.dissem.bitmessage.entity.valueobject.Label;
+import android.widget.CompoundButton;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
@@ -29,18 +30,34 @@ import com.mikepenz.materialdrawer.accountswitcher.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem;
-import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
+import com.mikepenz.materialdrawer.model.SwitchDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.model.interfaces.Nameable;
+import com.mikepenz.materialdrawer.model.interfaces.OnCheckedChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Timer;
-import java.util.TimerTask;
+
+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.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Plaintext;
+import ch.dissem.bitmessage.entity.valueobject.Label;
+import ch.dissem.bitmessage.ports.AddressRepository;
+import ch.dissem.bitmessage.ports.MessageRepository;
+
+import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_IDENTITY;
+import static ch.dissem.apps.abit.service.BitmessageService.MSG_START_NODE;
+import static ch.dissem.apps.abit.service.BitmessageService.MSG_STOP_NODE;
+import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
/**
@@ -66,6 +83,7 @@ public class MessageListActivity extends AppCompatActivity
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 int ADD_IDENTITY = 1;
/**
@@ -74,16 +92,36 @@ public class MessageListActivity extends AppCompatActivity
*/
private boolean twoPane;
- private AccountHeader accountHeader;
- private BitmessageContext bmc;
+ private static IncomingHandler incomingHandler = new IncomingHandler();
+ private static Messenger messenger = new Messenger(incomingHandler);
+ private static Messenger service;
+ private static boolean bound;
+ private static ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ MessageListActivity.service = new Messenger(service);
+ MessageListActivity.bound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ service = null;
+ bound = false;
+ }
+ };
+
private Label selectedLabel;
- private Menu menu;
+
+ private MessageRepository messageRepo;
+ private AddressRepository addressRepo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- bmc = Singleton.getBitmessageContext(this);
- selectedLabel = bmc.messages().getLabels().get(0);
+ messageRepo = Singleton.getMessageRepository(this);
+ addressRepo = Singleton.getAddressRepository(this);
+
+ selectedLabel = messageRepo.getLabels().get(0);
setContentView(R.layout.activity_message_list);
@@ -99,6 +137,10 @@ public class MessageListActivity extends AppCompatActivity
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
twoPane = true;
+
+ // In two-pane mode, list items should be given the
+ // 'activated' state when touched.
+ listFragment.setActivateOnItemClick(true);
}
createDrawer(toolbar);
@@ -109,16 +151,22 @@ public class MessageListActivity extends AppCompatActivity
if (getIntent().hasExtra(EXTRA_SHOW_MESSAGE)) {
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
}
+
+ createSyncAccount();
}
- @Override
- protected void onResume() {
- super.onResume();
- if (twoPane) {
- // In two-pane mode, list items should be given the
- // 'activated' state when touched.
- ((MessageListFragment) getSupportFragmentManager().findFragmentById(R.id.item_list))
- .setActivateOnItemClick(true);
+ 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);
}
}
@@ -138,7 +186,7 @@ public class MessageListActivity extends AppCompatActivity
private void createDrawer(Toolbar toolbar) {
final ArrayList profiles = new ArrayList<>();
- for (BitmessageAddress identity : bmc.addresses().getIdentities()) {
+ for (BitmessageAddress identity : addressRepo.getIdentities()) {
LOG.info("Adding identity " + identity.getAddress());
profiles.add(new ProfileDrawerItem()
.withIcon(new Identicon(identity))
@@ -161,7 +209,7 @@ public class MessageListActivity extends AppCompatActivity
.withIcon(GoogleMaterial.Icon.gmd_settings)
);
// Create the AccountHeader
- accountHeader = new AccountHeaderBuilder()
+ AccountHeader accountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.header)
.withProfiles(profiles)
@@ -169,16 +217,12 @@ public class MessageListActivity extends AppCompatActivity
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) {
- BitmessageAddress identity = bmc.createIdentity(false);
- IProfile newProfile = new ProfileDrawerItem()
- .withName(identity.toString())
- .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);
- } else {
- accountHeader.addProfiles(newProfile);
+ try {
+ Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY);
+ message.replyTo = messenger;
+ service.send(message);
+ } catch (RemoteException e) {
+ LOG.error(e.getMessage(), e);
}
}
// false if it should close the drawer
@@ -186,9 +230,10 @@ public class MessageListActivity extends AppCompatActivity
}
})
.build();
+ incomingHandler.updateAccountHeader(accountHeader);
ArrayList drawerItems = new ArrayList<>();
- for (Label label : bmc.messages().getLabels()) {
+ for (Label label : messageRepo.getLabels()) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label);
switch (label.getType()) {
case INBOX:
@@ -227,26 +272,43 @@ public class MessageListActivity extends AppCompatActivity
.withAccountHeader(accountHeader)
.withDrawerItems(drawerItems)
.addStickyDrawerItems(
- new SecondaryDrawerItem()
+ new PrimaryDrawerItem()
.withName(R.string.subscriptions)
.withIcon(CommunityMaterial.Icon.cmd_rss_box),
- new SecondaryDrawerItem()
+ new PrimaryDrawerItem()
.withName(R.string.settings)
- .withIcon(GoogleMaterial.Icon.gmd_settings)
+ .withIcon(GoogleMaterial.Icon.gmd_settings),
+ new SwitchDrawerItem()
+ .withName(R.string.full_node)
+ .withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
+ .withChecked(BitmessageService.isRunning())
+ .withOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) {
+ if (messenger != null) {
+ if (isChecked) {
+ try {
+ 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));
+ } catch (RemoteException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+ }
+ }
+ })
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(AdapterView> adapterView, View view, int i, long l, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
- if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment)) {
- MessageListFragment listFragment = new MessageListFragment();
- changeList(listFragment);
- listFragment.updateList(selectedLabel);
- } else {
- ((MessageListFragment) getSupportFragmentManager()
- .findFragmentById(R.id.item_list)).updateList(selectedLabel);
- }
+ showSelectedLabel();
return false;
} else if (item instanceof Nameable>) {
Nameable> ni = (Nameable>) item;
@@ -262,6 +324,12 @@ public class MessageListActivity extends AppCompatActivity
case R.string.settings:
startActivity(new Intent(MessageListActivity.this, SettingsActivity.class));
break;
+ case R.string.archive:
+ selectedLabel = null;
+ showSelectedLabel();
+ break;
+ case R.string.full_node:
+ return true;
}
}
return false;
@@ -271,34 +339,14 @@ public class MessageListActivity extends AppCompatActivity
.build();
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- this.menu = menu;
- updateMenu();
- return true;
- }
-
- private void updateMenu() {
- boolean running = bmc.isRunning();
- menu.findItem(R.id.sync_enabled).setVisible(running);
- menu.findItem(R.id.sync_disabled).setVisible(!running);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.sync_disabled:
- bmc.startup();
- new NetworkNotification(this).show();
- updateMenu();
- return true;
- case R.id.sync_enabled:
- bmc.shutdown();
- updateMenu();
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ private void showSelectedLabel() {
+ if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) {
+ ((MessageListFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.item_list)).updateList(selectedLabel);
+ } else {
+ MessageListFragment listFragment = new MessageListFragment();
+ changeList(listFragment);
+ listFragment.updateList(selectedLabel);
}
}
@@ -345,6 +393,7 @@ public class MessageListActivity extends AppCompatActivity
@Override
public void updateTitle(CharSequence title) {
+ //noinspection ConstantConditions
getSupportActionBar().setTitle(title);
}
@@ -352,4 +401,58 @@ public class MessageListActivity extends AppCompatActivity
return selectedLabel;
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onStop() {
+ if (bound) {
+ unbindService(connection);
+ bound = false;
+ }
+ super.onStop();
+ }
+
+ private static class IncomingHandler extends Handler {
+ private WeakReference accountHeaderRef;
+
+ private IncomingHandler() {
+ accountHeaderRef = new WeakReference<>(null);
+ }
+
+ public void updateAccountHeader(AccountHeader accountHeader){
+ accountHeaderRef = new WeakReference<>(accountHeader);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case BitmessageService.MSG_CREATE_IDENTITY: {
+ AccountHeader accountHeader = accountHeaderRef.get();
+ if (accountHeader == null) break;
+
+ Serializable data = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
+ if (data instanceof BitmessageAddress) {
+ BitmessageAddress identity = (BitmessageAddress) data;
+ IProfile newProfile = new ProfileDrawerItem()
+ .withName(identity.toString())
+ .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);
+ } else {
+ accountHeader.addProfiles(newProfile);
+ }
+ }
+ break;
+ }
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java
index f251d8a..802b901 100644
--- a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java
@@ -1,15 +1,24 @@
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;
import android.support.design.widget.FloatingActionButton;
-import android.view.*;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
+
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
+import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
@@ -52,11 +61,14 @@ public class MessageListFragment extends AbstractItemListFragment {
@Override
public void updateList(Label label) {
currentLabel = label;
+
+ if (!isVisible()) return;
+
setListAdapter(new ArrayAdapter(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
- bmc.messages().findMessages(label)) {
+ Singleton.getMessageRepository(getContext()).findMessages(label)) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
@@ -81,7 +93,11 @@ public class MessageListFragment extends AbstractItemListFragment {
}
});
if (getActivity() instanceof ActionBarListener) {
- ((ActionBarListener) getActivity()).updateTitle(label.toString());
+ if (label != null) {
+ ((ActionBarListener) getActivity()).updateTitle(label.toString());
+ } else {
+ ((ActionBarListener) getActivity()).updateTitle(getString(R.string.archive));
+ }
}
if (emptyTrashMenuItem != null) {
emptyTrashMenuItem.setVisible(label != null && label.getType() == Label.Type.TRASH);
@@ -117,7 +133,7 @@ public class MessageListFragment extends AbstractItemListFragment {
case R.id.empty_trash:
if (currentLabel.getType() != Label.Type.TRASH) return true;
- MessageRepository repo = bmc.messages();
+ MessageRepository repo = Singleton.getMessageRepository(getContext());
for (Plaintext message : repo.findMessages(currentLabel)) {
repo.remove(message);
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java b/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java
index cff4772..17ffe41 100644
--- a/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java
+++ b/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java
@@ -17,19 +17,52 @@
package ch.dissem.apps.abit;
import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
-import ch.dissem.apps.abit.service.Singleton;
-import ch.dissem.bitmessage.BitmessageContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.dissem.apps.abit.service.BitmessageService;
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);
+
+ private Messenger service;
+ private boolean bound;
+ private ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ OpenBitmessageLinkActivity.this.service = new Messenger(service);
+ OpenBitmessageLinkActivity.this.bound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ service = null;
+ bound = false;
+ }
+ };
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -70,14 +103,29 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- BitmessageContext bmc = Singleton.getBitmessageContext(OpenBitmessageLinkActivity.this);
BitmessageAddress bmAddress = new BitmessageAddress(address);
bmAddress.setAlias(label.getText().toString());
- if (subscribe.isChecked()) {
- bmc.addSubscribtion(bmAddress);
- }
- if (importContact.isChecked()) {
- bmc.addContact(bmAddress);
+
+ final int what;
+ if (subscribe.isChecked() && importContact.isChecked())
+ what = MSG_SUBSCRIBE_AND_ADD_CONTACT;
+ else if (subscribe.isChecked())
+ what = MSG_SUBSCRIBE;
+ else if (importContact.isChecked())
+ what = MSG_ADD_CONTACT;
+ else
+ what = 0;
+
+ if (what != 0) {
+ try {
+ Message message = Message.obtain(null, what);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DATA_FIELD_ADDRESS, bmAddress);
+ message.setData(bundle);
+ service.send(message);
+ } catch (RemoteException e) {
+ LOG.error(e.getMessage(), e);
+ }
}
setResult(Activity.RESULT_OK);
finish();
@@ -110,4 +158,19 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
return new String[0];
}
}
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onStop() {
+ if (bound) {
+ unbindService(connection);
+ bound = false;
+ }
+ super.onStop();
+ }
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java
index fa16aec..0fa31e8 100644
--- a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java
@@ -27,6 +27,7 @@ import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
+
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
@@ -115,7 +116,7 @@ public class SubscriptionDetailFragment extends Fragment {
@Override
public void onPause() {
- Singleton.getBitmessageContext(getActivity()).addresses().save(item);
+ Singleton.getAddressRepository(getContext()).save(item);
super.onPause();
}
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java
index a3c7b00..18fa320 100644
--- a/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java
@@ -24,6 +24,8 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
+
+import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label;
@@ -32,7 +34,7 @@ import java.util.Comparator;
import java.util.List;
/**
- * Created by chris on 06.09.15.
+ * Fragment that shows a list of all contacts, the ones we subscribed to first.
*/
public class SubscriptionListFragment extends AbstractItemListFragment {
@Override
@@ -43,7 +45,7 @@ public class SubscriptionListFragment extends AbstractItemListFragment addresses = bmc.addresses().getContacts();
+ List addresses = Singleton.getAddressRepository(getContext()).getContacts();
Collections.sort(addresses, new Comparator() {
/**
* Yields the following order:
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java
index 60bb86d..21ff505 100644
--- a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java
@@ -10,11 +10,11 @@ import android.content.Context;
public abstract class AbstractNotification {
protected final Context ctx;
protected final NotificationManager manager;
- public Notification notification;
+ protected Notification notification;
public AbstractNotification(Context ctx) {
- this.ctx = ctx;
+ this.ctx = ctx.getApplicationContext();
this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
}
@@ -23,6 +23,10 @@ public abstract class AbstractNotification {
*/
protected abstract int getNotificationId();
+ public Notification getNotification() {
+ return notification;
+ }
+
public void show() {
manager.notify(getNotificationId(), notification);
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java
new file mode 100644
index 0000000..c64185e
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java
@@ -0,0 +1,42 @@
+package ch.dissem.apps.abit.notification;
+
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.support.v7.app.NotificationCompat;
+
+import ch.dissem.apps.abit.R;
+
+/**
+ * Created by chrigu on 29.10.15.
+ */
+public class ErrorNotification extends AbstractNotification {
+ public static final int ERROR_NOTIFICATION_ID = 4;
+
+ private NotificationCompat.Builder builder;
+
+ public ErrorNotification(Context ctx) {
+ super(ctx);
+ builder = new NotificationCompat.Builder(ctx);
+ builder.setContentTitle(ctx.getString(R.string.app_name))
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ }
+
+ public ErrorNotification setWarning(@StringRes int resId, Object... args) {
+ builder.setSmallIcon(R.drawable.ic_notification_warning)
+ .setContentText(ctx.getString(resId, args));
+ notification = builder.build();
+ return this;
+ }
+
+ public ErrorNotification setError(@StringRes int resId, Object... args) {
+ builder.setSmallIcon(R.drawable.ic_notification_error)
+ .setContentText(ctx.getString(resId, args));
+ notification = builder.build();
+ return this;
+ }
+
+ @Override
+ protected int getNotificationId() {
+ return ERROR_NOTIFICATION_ID;
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java
index b51cc47..bff6e92 100644
--- a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java
@@ -1,6 +1,7 @@
package ch.dissem.apps.abit.notification;
import android.annotation.SuppressLint;
+import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -11,7 +12,6 @@ import java.util.TimerTask;
import ch.dissem.apps.abit.MessageListActivity;
import ch.dissem.apps.abit.R;
-import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.utils.Property;
@@ -19,18 +19,26 @@ import ch.dissem.bitmessage.utils.Property;
* Shows the network status (as long as the client is connected as a full node)
*/
public class NetworkNotification extends AbstractNotification {
+ public static final int ONGOING_NOTIFICATION_ID = 2;
+
private final BitmessageContext bmc;
private NotificationCompat.Builder builder;
- public NetworkNotification(Context ctx) {
+ public NetworkNotification(Context ctx, BitmessageContext bmc) {
super(ctx);
- bmc = Singleton.getBitmessageContext(ctx);
+ this.bmc = bmc;
builder = new NotificationCompat.Builder(ctx);
builder.setSmallIcon(R.drawable.ic_notification_full_node)
- .setContentTitle(ctx.getString(R.string.bitmessage_active))
+ .setContentTitle(ctx.getString(R.string.bitmessage_full_node))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
}
+ @Override
+ public Notification getNotification() {
+ update();
+ return notification;
+ }
+
@SuppressLint("StringFormatMatches")
private boolean update() {
boolean running = bmc.isRunning();
@@ -82,6 +90,6 @@ public class NetworkNotification extends AbstractNotification {
@Override
protected int getNotificationId() {
- return 2;
+ return ONGOING_NOTIFICATION_ID;
}
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java
index ee10bf1..4e66ab1 100644
--- a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java
@@ -1,6 +1,5 @@
package ch.dissem.apps.abit.notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -21,6 +20,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import static ch.dissem.apps.abit.util.Drawables.toBitmap;
public class NewMessageNotification extends AbstractNotification {
+ public static final int NEW_MESSAGE_NOTIFICATION_ID = 1;
private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD);
public NewMessageNotification(Context ctx) {
@@ -76,6 +76,6 @@ public class NewMessageNotification extends AbstractNotification {
@Override
protected int getNotificationId() {
- return 1;
+ return NEW_MESSAGE_NOTIFICATION_ID;
}
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java
new file mode 100644
index 0000000..f917a83
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java
@@ -0,0 +1,38 @@
+package ch.dissem.apps.abit.notification;
+
+import android.app.PendingIntent;
+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.R;
+
+/**
+ * Ongoing notification while proof of work is in progress.
+ */
+public class ProofOfWorkNotification extends AbstractNotification {
+ public static final int ONGOING_NOTIFICATION_ID = 3;
+
+ public ProofOfWorkNotification(Context ctx) {
+ super(ctx);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx);
+
+ Intent showMessageIntent = new Intent(ctx, MessageListActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setUsesChronometer(true)
+ .setSmallIcon(R.drawable.ic_notification_proof_of_work)
+ .setContentTitle(ctx.getString(R.string.proof_of_work_title))
+ .setContentText(ctx.getString(R.string.proof_of_work_text))
+ .setContentIntent(pendingIntent);
+
+ notification = builder.build();
+ }
+
+ @Override
+ protected int getNotificationId() {
+ return ONGOING_NOTIFICATION_ID;
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java
index 32b9ed8..5e5f1c5 100644
--- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java
+++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java
@@ -187,7 +187,7 @@ public class AndroidInventory implements Inventory {
"hash = X'" + object.getInventoryVector() + "'",
null, null, null, null
);
- return c.getColumnCount() > 0;
+ return c.getCount() > 0;
}
@Override
diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java
new file mode 100644
index 0000000..e52eb95
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java
@@ -0,0 +1,164 @@
+package ch.dissem.apps.abit.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+
+import ch.dissem.apps.abit.notification.NetworkNotification;
+import ch.dissem.bitmessage.BitmessageContext;
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+
+import static ch.dissem.apps.abit.notification.NetworkNotification.ONGOING_NOTIFICATION_ID;
+
+/**
+ * Define a Service that returns an IBinder for the
+ * sync adapter class, allowing the sync adapter framework to call
+ * onPerformSync().
+ */
+public class BitmessageService extends Service {
+ public static final Logger LOG = LoggerFactory.getLogger(BitmessageService.class);
+
+ 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;
+ public static final int MSG_STOP_NODE = 101;
+
+ public static final String DATA_FIELD_IDENTITY = "identity";
+ public static final String DATA_FIELD_ADDRESS = "address";
+ public static final String DATA_FIELD_SUBJECT = "subject";
+ public static final String DATA_FIELD_MESSAGE = "message";
+
+ // Object to use as a thread-safe lock
+ private static final Object lock = new Object();
+
+ private static NetworkNotification notification = null;
+ private static BitmessageContext bmc = null;
+
+ private static volatile boolean running = false;
+
+ private static Messenger messenger;
+
+ public static boolean isRunning() {
+ return running;
+ }
+
+ @Override
+ public void onCreate() {
+ synchronized (lock) {
+ if (bmc == null) {
+ bmc = Singleton.getBitmessageContext(this);
+ notification = new NetworkNotification(this, bmc);
+ messenger = new Messenger(new IncomingHandler(this));
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return Service.START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (bmc.isRunning()) bmc.shutdown();
+ running = false;
+ }
+
+ /**
+ * Return an object that allows the system to invoke
+ * the sync adapter.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return messenger.getBinder();
+ }
+
+ private static class IncomingHandler extends Handler {
+ private WeakReference service;
+
+ private IncomingHandler(BitmessageService service) {
+ this.service = new WeakReference<>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CREATE_IDENTITY: {
+ BitmessageAddress identity = bmc.createIdentity(false);
+ if (msg.replyTo != null) {
+ try {
+ Message message = Message.obtain(this, MSG_CREATE_IDENTITY);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(DATA_FIELD_IDENTITY, identity);
+ message.setData(bundle);
+ msg.replyTo.send(message);
+ } catch (RemoteException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ }
+ break;
+ }
+ case MSG_SUBSCRIBE: {
+ Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
+ if (data instanceof BitmessageAddress) {
+ bmc.addSubscribtion((BitmessageAddress) data);
+ }
+ break;
+ }
+ case MSG_SEND_MESSAGE: {
+ Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
+ Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
+ if (identity instanceof BitmessageAddress
+ && address instanceof BitmessageAddress) {
+ String subject = msg.getData().getString(DATA_FIELD_SUBJECT);
+ String message = msg.getData().getString(DATA_FIELD_MESSAGE);
+ bmc.send((BitmessageAddress) identity, (BitmessageAddress) address,
+ subject, message);
+ }
+ break;
+ }
+ case MSG_SEND_BROADCAST: {
+ Serializable data = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
+ if (data instanceof BitmessageAddress) {
+ String subject = msg.getData().getString(DATA_FIELD_SUBJECT);
+ String message = msg.getData().getString(DATA_FIELD_MESSAGE);
+ bmc.broadcast((BitmessageAddress) data, subject, message);
+ }
+ break;
+ }
+ case MSG_START_NODE:
+ // TODO: warn user, option to restrict to WiFi
+ // (I'm not quite sure this can be done here, though)
+ service.get().startService(new Intent(service.get(), BitmessageService.class));
+ running = true;
+ service.get().startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
+ bmc.startup();
+ notification.show();
+ break;
+ case MSG_STOP_NODE:
+ bmc.shutdown();
+ running = false;
+ service.get().stopForeground(false);
+ service.get().stopSelf();
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java
new file mode 100644
index 0000000..7d86ef8
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java
@@ -0,0 +1,88 @@
+package ch.dissem.apps.abit.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.support.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.ref.WeakReference;
+
+import ch.dissem.apps.abit.notification.ProofOfWorkNotification;
+import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
+import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
+
+import static ch.dissem.apps.abit.notification.ProofOfWorkNotification.ONGOING_NOTIFICATION_ID;
+
+/**
+ * The Proof of Work Service makes sure POW is done in a foreground process, so it shouldn't be
+ * killed by the system before the nonce is found.
+ */
+public class ProofOfWorkService extends Service {
+ public static final Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
+
+ // Object to use as a thread-safe lock
+ private static final Object lock = new Object();
+ private static ProofOfWorkEngine engine;
+
+ @Override
+ public void onCreate() {
+ synchronized (lock) {
+ if (engine == null) {
+ engine = new MultiThreadedPOWEngine();
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new PowBinder(engine, this);
+ }
+
+ public static class PowBinder extends Binder {
+ private final ProofOfWorkEngine engine;
+
+ private PowBinder(ProofOfWorkEngine engine, ProofOfWorkService service) {
+ this.engine = new EngineWrapper(engine, service);
+ }
+
+ public ProofOfWorkEngine getEngine() {
+ return engine;
+ }
+ }
+
+ private static class EngineWrapper implements ProofOfWorkEngine {
+ private final ProofOfWorkNotification notification;
+ private final ProofOfWorkEngine engine;
+ private final WeakReference serviceRef;
+
+ private EngineWrapper(ProofOfWorkEngine engine, ProofOfWorkService service) {
+ this.engine = engine;
+ this.serviceRef = new WeakReference<>(service);
+ this.notification = new ProofOfWorkNotification(service);
+ }
+
+ @Override
+ public void calculateNonce(byte[] initialHash, byte[] target, final Callback callback) {
+ final ProofOfWorkService service = serviceRef.get();
+ service.startService(new Intent(service, ProofOfWorkService.class));
+ service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
+ engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
+ @Override
+ public void onNonceCalculated(byte[] nonce) {
+ try {
+ callback.onNonceCalculated(nonce);
+ } finally {
+ service.stopForeground(true);
+ service.stopSelf();
+ }
+ }
+ });
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java b/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java
new file mode 100644
index 0000000..16d2aaa
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java
@@ -0,0 +1,61 @@
+package ch.dissem.apps.abit.service;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import java.util.concurrent.Semaphore;
+
+import ch.dissem.apps.abit.service.ProofOfWorkService.PowBinder;
+import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+
+/**
+ * Proof of Work engine that uses the Proof of Work service.
+ */
+public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Callback {
+ private final Semaphore semaphore = new Semaphore(1, true);
+ private final Context ctx;
+
+ private byte[] initialHash, targetValue;
+ private Callback callback;
+
+ public ServicePowEngine(Context ctx) {
+ this.ctx = ctx;
+ }
+
+ private ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ ((PowBinder) service).getEngine().calculateNonce(initialHash, targetValue, ServicePowEngine.this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ semaphore.release();
+ }
+ };
+
+ @Override
+ public void calculateNonce(byte[] initialHash, byte[] targetValue, Callback callback) {
+ try {
+ semaphore.acquire();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ this.initialHash = initialHash;
+ this.targetValue = targetValue;
+ this.callback = callback;
+ ctx.bindService(new Intent(ctx, ProofOfWorkService.class), connection, BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onNonceCalculated(byte[] bytes) {
+ callback.onNonceCalculated(bytes);
+ ctx.unbindService(connection);
+ }
+
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java
index 220dc36..a178519 100644
--- a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java
+++ b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java
@@ -9,23 +9,27 @@ import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.bitmessage.BitmessageContext;
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;
/**
* Provides singleton objects across the application.
*/
public class Singleton {
+ public static final Object lock = new Object();
private static BitmessageContext bitmessageContext;
private static MessageListener messageListener;
public static BitmessageContext getBitmessageContext(Context context) {
if (bitmessageContext == null) {
- synchronized (Singleton.class) {
+ synchronized (lock) {
if (bitmessageContext == null) {
final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx);
bitmessageContext = new BitmessageContext.Builder()
+ .proofOfWorkEngine(new ServicePowEngine(ctx))
.security(new SpongySecurity())
.nodeRegistry(new MemoryNodeRegistry())
.inventory(new AndroidInventory(sqlHelper))
@@ -50,4 +54,12 @@ public class Singleton {
}
return messageListener;
}
+
+ public static MessageRepository getMessageRepository(Context ctx) {
+ return getBitmessageContext(ctx).messages();
+ }
+
+ public static AddressRepository getAddressRepository(Context ctx) {
+ return getBitmessageContext(ctx).addresses();
+ }
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/Authenticator.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/Authenticator.java
new file mode 100644
index 0000000..6d4eec5
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/Authenticator.java
@@ -0,0 +1,82 @@
+package ch.dissem.apps.abit.synchronization;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.NetworkErrorException;
+import android.content.Context;
+import android.os.Bundle;
+
+/*
+ * Implement AbstractAccountAuthenticator and stub out all
+ * of its methods
+ */
+public class Authenticator extends AbstractAccountAuthenticator {
+ public static final String ACCOUNT_NAME = "Bitmessage";
+ public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage";
+
+ // Simple constructor
+ public Authenticator(Context context) {
+ super(context);
+ }
+
+ // Editing properties is not supported
+ @Override
+ public Bundle editProperties(
+ AccountAuthenticatorResponse r, String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ // Don't add additional accounts
+ @Override
+ public Bundle addAccount(
+ AccountAuthenticatorResponse r,
+ String s,
+ String s2,
+ String[] strings,
+ Bundle bundle) throws NetworkErrorException {
+ return null;
+ }
+
+ // Ignore attempts to confirm credentials
+ @Override
+ public Bundle confirmCredentials(
+ AccountAuthenticatorResponse r,
+ Account account,
+ Bundle bundle) throws NetworkErrorException {
+ return null;
+ }
+
+ // Getting an authentication token is not supported
+ @Override
+ public Bundle getAuthToken(
+ AccountAuthenticatorResponse r,
+ Account account,
+ String s,
+ Bundle bundle) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+
+ // Getting a label for the auth token is not supported
+ @Override
+ public String getAuthTokenLabel(String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ // Updating user credentials is not supported
+ @Override
+ public Bundle updateCredentials(
+ AccountAuthenticatorResponse r,
+ Account account,
+ String s, Bundle bundle) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+
+ // Checking features for the account is not supported
+ @Override
+ public Bundle hasFeatures(
+ AccountAuthenticatorResponse r,
+ Account account, String[] strings) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/AuthenticatorService.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/AuthenticatorService.java
new file mode 100644
index 0000000..8fe717d
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/AuthenticatorService.java
@@ -0,0 +1,31 @@
+package ch.dissem.apps.abit.synchronization;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * A bound Service that instantiates the authenticator
+ * when started.
+ */
+public class AuthenticatorService extends Service {
+ /**
+ * Instance field that stores the authenticator object
+ */
+ private Authenticator authenticator;
+
+ @Override
+ public void onCreate() {
+ // Create a new authenticator object
+ authenticator = new Authenticator(this);
+ }
+
+ /*
+ * When the system binds to this Service to make the RPC call
+ * return the authenticator's IBinder.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return authenticator.getIBinder();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/StubProvider.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/StubProvider.java
new file mode 100644
index 0000000..f5b3d2d
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/StubProvider.java
@@ -0,0 +1,72 @@
+package ch.dissem.apps.abit.synchronization;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/*
+ * Define an implementation of ContentProvider that stubs out
+ * all methods
+ */
+public class StubProvider extends ContentProvider {
+ public static final String AUTHORITY = "ch.dissem.apps.abit.provider";
+
+ /*
+ * Always return true, indicating that the
+ * provider loaded correctly.
+ */
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ /*
+ * Return no type for MIME type
+ */
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ /*
+ * query() always returns no results
+ *
+ */
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ /*
+ * insert() always returns null (no URI)
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ /*
+ * delete() always returns "no rows affected" (0)
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ /*
+ * update() always returns "no rows affected" (0)
+ */
+ public int update(
+ Uri uri,
+ ContentValues values,
+ String selection,
+ String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java
new file mode 100644
index 0000000..7654833
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java
@@ -0,0 +1,85 @@
+package ch.dissem.apps.abit.synchronization;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+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 ch.dissem.apps.abit.R;
+import ch.dissem.apps.abit.notification.ErrorNotification;
+import ch.dissem.apps.abit.service.Singleton;
+import ch.dissem.bitmessage.BitmessageContext;
+
+/**
+ * Sync Adapter to synchronize with the Bitmessage network - fetches
+ * new objects and then disconnects.
+ */
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+ private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
+
+ private final BitmessageContext bmc;
+
+ /**
+ * Set up the sync adapter
+ */
+ public SyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ bmc = Singleton.getBitmessageContext(context);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
+ // 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");
+ return;
+ }
+ 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);
+ 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);
+ }
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncService.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncService.java
new file mode 100644
index 0000000..b3abcaf
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncService.java
@@ -0,0 +1,65 @@
+package ch.dissem.apps.abit.synchronization;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.dissem.apps.abit.listener.MessageListener;
+import ch.dissem.apps.abit.notification.NetworkNotification;
+import ch.dissem.apps.abit.repository.AndroidInventory;
+import ch.dissem.apps.abit.repository.SqlHelper;
+import ch.dissem.apps.abit.service.Singleton;
+import ch.dissem.bitmessage.BitmessageContext;
+import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
+import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
+import ch.dissem.bitmessage.security.sc.SpongySecurity;
+
+import static ch.dissem.apps.abit.notification.NetworkNotification.ONGOING_NOTIFICATION_ID;
+
+/**
+ * Define a Service that returns an IBinder for the
+ * sync adapter class, allowing the sync adapter framework to call
+ * onPerformSync().
+ */
+public class SyncService extends Service {
+ private static final Logger LOG = LoggerFactory.getLogger(SyncService.class);
+ // Storage for an instance of the sync adapter
+ private static SyncAdapter syncAdapter = null;
+ // Object to use as a thread-safe lock
+ private static final Object syncAdapterLock = new Object();
+
+ /**
+ * Instantiate the sync adapter object.
+ */
+ @Override
+ public void onCreate() {
+ /*
+ * Create the sync adapter as a singleton.
+ * Set the sync adapter as syncable
+ * Disallow parallel syncs
+ */
+ synchronized (syncAdapterLock) {
+ if (syncAdapter == null) {
+ syncAdapter = new SyncAdapter(this, true);
+ }
+ }
+ }
+
+ /**
+ * Return an object that allows the system to invoke
+ * the sync adapter.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ /*
+ * Get the object that allows external processes
+ * to call onPerformSync(). The object is created
+ * in the base class code when the SyncAdapter
+ * constructors call super()
+ */
+ return syncAdapter.getSyncAdapterBinder();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_action_notification_sync.png b/app/src/main/res/drawable-hdpi/ic_action_notification_sync.png
deleted file mode 100755
index d938029..0000000
Binary files a/app/src/main/res/drawable-hdpi/ic_action_notification_sync.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_notification_sync_disabled.png b/app/src/main/res/drawable-hdpi/ic_action_notification_sync_disabled.png
deleted file mode 100755
index c8bcece..0000000
Binary files a/app/src/main/res/drawable-hdpi/ic_action_notification_sync_disabled.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_notification_sync.png b/app/src/main/res/drawable-mdpi/ic_action_notification_sync.png
deleted file mode 100755
index f051d5e..0000000
Binary files a/app/src/main/res/drawable-mdpi/ic_action_notification_sync.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_notification_sync_disabled.png b/app/src/main/res/drawable-mdpi/ic_action_notification_sync_disabled.png
deleted file mode 100755
index d8e4526..0000000
Binary files a/app/src/main/res/drawable-mdpi/ic_action_notification_sync_disabled.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_notification_sync.png b/app/src/main/res/drawable-xhdpi/ic_action_notification_sync.png
deleted file mode 100755
index 7e1ffed..0000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_action_notification_sync.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_notification_sync_disabled.png b/app/src/main/res/drawable-xhdpi/ic_action_notification_sync_disabled.png
deleted file mode 100755
index 00e51af..0000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_action_notification_sync_disabled.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync.png b/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync.png
deleted file mode 100755
index 4017dab..0000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync_disabled.png b/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync_disabled.png
deleted file mode 100755
index 549cf6d..0000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_notification_sync_disabled.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync.png b/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync.png
deleted file mode 100755
index bea0812..0000000
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync_disabled.png b/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync_disabled.png
deleted file mode 100755
index d71a99a..0000000
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_action_notification_sync_disabled.png and /dev/null differ
diff --git a/app/src/main/res/drawable/ic_notification_error.xml b/app/src/main/res/drawable/ic_notification_error.xml
new file mode 100644
index 0000000..bc132bb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification_error.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notification_proof_of_work.xml b/app/src/main/res/drawable/ic_notification_proof_of_work.xml
new file mode 100644
index 0000000..c64f014
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification_proof_of_work.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notification_warning.xml b/app/src/main/res/drawable/ic_notification_warning.xml
new file mode 100644
index 0000000..0510991
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification_warning.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout-sw600dp/activity_message_list.xml b/app/src/main/res/layout-w720dp/activity_message_list.xml
similarity index 100%
rename from app/src/main/res/layout-sw600dp/activity_message_list.xml
rename to app/src/main/res/layout-w720dp/activity_message_list.xml
diff --git a/app/src/main/res/layout/activity_open_bitmessage_link.xml b/app/src/main/res/layout/activity_open_bitmessage_link.xml
index 95f792d..fedbd9d 100644
--- a/app/src/main/res/layout/activity_open_bitmessage_link.xml
+++ b/app/src/main/res/layout/activity_open_bitmessage_link.xml
@@ -1,83 +1,81 @@
-
+
+ android:id="@+id/address"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ android:id="@+id/label_wrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/address">
+ android:id="@+id/label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/label"
+ android:inputType="textPersonName" />
+ android:id="@+id/import_contact"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/label_wrapper"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="8dp"
+ android:text="@string/import_contact" />
+ android:id="@+id/subscribe"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/import_contact"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="8dp"
+ android:layout_marginTop="8dp"
+ android:text="@string/subscribe" />
+ android:id="@+id/do_import"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:layout_below="@+id/subscribe"
+ android:layout_marginBottom="12dp"
+ android:layout_marginTop="12dp"
+ android:text="@string/do_import" />
+ android:id="@+id/compose_message"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/do_import"
+ android:layout_below="@+id/subscribe"
+ android:layout_toLeftOf="@+id/do_import"
+ android:layout_toStartOf="@+id/do_import"
+ android:text="@string/write_message" />
+ android:id="@+id/cancel"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/compose_message"
+ android:layout_toLeftOf="@+id/compose_message"
+ android:layout_toStartOf="@+id/compose_message"
+ android:text="@string/cancel" />
diff --git a/app/src/main/res/layout/fragment_message_detail.xml b/app/src/main/res/layout/fragment_message_detail.xml
index b10bfab..55e0516 100644
--- a/app/src/main/res/layout/fragment_message_detail.xml
+++ b/app/src/main/res/layout/fragment_message_detail.xml
@@ -1,82 +1,82 @@
-
+
+
+
-
-
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:elegantTextHeight="false"
+ android:enabled="false"
+ android:gravity="center_vertical"
+ android:padding="16dp"
+ android:text="Subject"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+ android:id="@+id/divider"
+ android:layout_width="fill_parent"
+ android:layout_height="2dip"
+ android:layout_below="@id/subject"
+ android:background="@color/divider" />
+ android:id="@+id/avatar"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_below="@+id/divider"
+ android:layout_margin="16dp"
+ android:src="@color/accent" />
+ android:id="@+id/sender"
+ android:layout_width="wrap_content"
+ android:layout_height="20dp"
+ android:layout_alignTop="@+id/avatar"
+ android:layout_toEndOf="@+id/avatar"
+ android:layout_toRightOf="@+id/avatar"
+ android:gravity="center_vertical"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
+ android:text="Sender"
+ android:textStyle="bold" />
+ android:id="@+id/recipient"
+ android:layout_width="wrap_content"
+ android:layout_height="20dp"
+ android:layout_alignBottom="@+id/avatar"
+ android:layout_toEndOf="@+id/avatar"
+ android:layout_toRightOf="@+id/avatar"
+ android:gravity="center_vertical"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
+ android:text="Recipient" />
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_below="@+id/avatar"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:layout_marginTop="32dp"
+ android:paddingBottom="64dp"
+ android:text="New Text"
+ android:textIsSelectable="true" />
\ No newline at end of file
diff --git a/app/src/main/res/menu/compose.xml b/app/src/main/res/menu/compose.xml
index ee25c89..dae8007 100644
--- a/app/src/main/res/menu/compose.xml
+++ b/app/src/main/res/menu/compose.xml
@@ -5,5 +5,5 @@
android:id="@+id/send"
app:showAsAction="always"
android:icon="@drawable/ic_action_send"
- android:title="@string/disable_sync"/>`
+ android:title="@string/send"/>`
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 9f35ce1..9eb7100 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -1,14 +1,4 @@
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d296eca..7020bf7 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -4,9 +4,7 @@
Einstellungen
Nur WLAN
Nicht mit Mobilfunknetz verbinden
- Bitmessage ist aktiv
- Synchronisieren ausschalten
- Synchronisieren einschalten
+ Bitmessage Netzknoten
Betreff
An
Nachricht
@@ -33,8 +31,19 @@
Stream %d
Aktiv
Abonnement
+ Zeitbeschränkung der Synchronisierung
+ Timeout in Sekunden
+ Vertrauenswürdiger Knoten
+ Diese Adresse wird für die Synchronisation verwendet
+ Aktiver Knoten
+ Senden
+ Schreiben
Stream %1$d: eine Verbindung
Stream %1$d: %2$d Verbindungen
Getrennt
Verbindung wird aufgebaut…
+ Warnung: dies könnte das Gerät erwärmen bis die Batterie leer ist.
+ Proof of Work
+ Synchronisation fehlgeschlagen: der vertrauenswürdige Knoten konnte nicht erreicht werden.
+ Ungültiger Port in den Synchronisationseinstellungen: %s
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index baf9d23..ed1cd40 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,9 +2,7 @@
Abit
Message Detail
Subscription Detail
- Disable Sync
- Enable Sync
- Bitmessage active
+ Bitmessage Node
Wi-Fi Connection Mode
Settings
Wi-Fi only
@@ -35,6 +33,17 @@
Empty Trash
Stream #%d
Enabled
+ Trusted node
+ Use this node for synchronization
+ Synchronization Timeout
+ Timeout in seconds
+ Write message
+ Full node
+ Send
Disconnected
Connecting…
+ Proof of Work
+ Warning: This might heat your device until the battery\'s dead.
+ Invalid port in synchronization settings: %s
+ Synchronization failed: Trusted node could not be reached.
diff --git a/app/src/main/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml
new file mode 100644
index 0000000..17609a0
--- /dev/null
+++ b/app/src/main/res/xml/authenticator.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index e1f7327..d026e3f 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -9,8 +9,19 @@
android:entryValues="@array/connection_mode_values"/>
-->
+ android:defaultValue="true"
+ android:key="wifi_only"
+ android:summary="@string/wifi_only_summary"
+ android:title="@string/wifi_only" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/syncadapter.xml b/app/src/main/res/xml/syncadapter.xml
new file mode 100644
index 0000000..fabc86f
--- /dev/null
+++ b/app/src/main/res/xml/syncadapter.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file