From af2bfc796b76ab594532a386c77f6397f52899a2 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 12 Sep 2016 11:00:00 +0200 Subject: [PATCH 1/3] Use the nio network listener. --- app/build.gradle | 10 +- .../db/migration/V3.3__Create_table_node.sql | 9 + .../apps/abit/ComposeMessageActivity.java | 1 + .../apps/abit/ComposeMessageFragment.java | 22 ++- .../apps/abit/MessageDetailFragment.java | 22 ++- .../repository/AndroidAddressRepository.java | 40 ++-- .../abit/repository/AndroidInventory.java | 47 ++--- .../repository/AndroidMessageRepository.java | 108 +++++----- .../abit/repository/AndroidNodeRegistry.java | 187 ++++++++++++++++++ .../AndroidProofOfWorkRepository.java | 69 ++++--- .../apps/abit/repository/SqlHelper.java | 10 +- .../dissem/apps/abit/service/Singleton.java | 11 +- build.gradle | 2 +- 13 files changed, 382 insertions(+), 156 deletions(-) create mode 100644 app/src/main/assets/db/migration/V3.3__Create_table_node.sql create mode 100644 app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java diff --git a/app/build.gradle b/app/build.gradle index e600ee0..3ba3317 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,7 +11,7 @@ if (project.hasProperty("project.configs") android { compileSdkVersion 24 - buildToolsVersion "24.0.1" + buildToolsVersion "24.0.2" defaultConfig { applicationId "ch.dissem.apps." + appName.toLowerCase() @@ -29,12 +29,12 @@ android { } } -ext.jabitVersion = 'develop-SNAPSHOT' +ext.jabitVersion = 'feature-nio-SNAPSHOT' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:24.1.1' - compile 'com.android.support:support-v4:24.1.1' - compile 'com.android.support:design:24.1.1' + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:support-v4:24.2.0' + compile 'com.android.support:design:24.2.0' compile "ch.dissem.jabit:jabit-core:$jabitVersion" compile "ch.dissem.jabit:jabit-networking:$jabitVersion" diff --git a/app/src/main/assets/db/migration/V3.3__Create_table_node.sql b/app/src/main/assets/db/migration/V3.3__Create_table_node.sql new file mode 100644 index 0000000..5d03bb5 --- /dev/null +++ b/app/src/main/assets/db/migration/V3.3__Create_table_node.sql @@ -0,0 +1,9 @@ +CREATE TABLE Node ( + stream BIGINT NOT NULL, + address BINARY(32) NOT NULL, + port INT NOT NULL, + services BIGINT NOT NULL, + time BIGINT NOT NULL, + PRIMARY KEY (stream, address, port) +); +CREATE INDEX idx_time on Node(time); diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java index 5fa12a4..c5f2032 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java @@ -27,6 +27,7 @@ public class ComposeMessageActivity extends AppCompatActivity { public static final String EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER"; public static final String EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT"; public static final String EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT"; + public static final String EXTRA_CONTENT = "ch.dissem.abit.Message.CONTENT"; @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java index b19ef4d..5d80e31 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java @@ -18,6 +18,7 @@ package ch.dissem.apps.abit; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.text.Selection; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -34,6 +35,7 @@ import ch.dissem.apps.abit.adapter.ContactAdapter; import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.entity.BitmessageAddress; +import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_CONTENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT; @@ -45,6 +47,7 @@ public class ComposeMessageFragment extends Fragment { private BitmessageAddress identity; private BitmessageAddress recipient; private String subject; + private String content; private AutoCompleteTextView recipientInput; private EditText subjectInput; private EditText bodyInput; @@ -71,6 +74,9 @@ public class ComposeMessageFragment extends Fragment { if (getArguments().containsKey(EXTRA_SUBJECT)) { subject = getArguments().getString(EXTRA_SUBJECT); } + if (getArguments().containsKey(EXTRA_CONTENT)) { + content = getArguments().getString(EXTRA_CONTENT); + } } else { throw new RuntimeException("No identity set for ComposeMessageFragment"); } @@ -106,6 +112,16 @@ public class ComposeMessageFragment extends Fragment { subjectInput = (EditText) rootView.findViewById(R.id.subject); subjectInput.setText(subject); bodyInput = (EditText) rootView.findViewById(R.id.body); + bodyInput.setText(content); + + if (recipient == null) { + recipientInput.requestFocus(); + } else if (subject == null || subject.isEmpty()) { + subjectInput.requestFocus(); + } else { + bodyInput.requestFocus(); + bodyInput.setSelection(0); + } return rootView; } @@ -126,7 +142,7 @@ public class ComposeMessageFragment extends Fragment { recipient = new BitmessageAddress(inputString); } catch (Exception e) { List contacts = Singleton.getAddressRepository - (getContext()).getContacts(); + (getContext()).getContacts(); for (BitmessageAddress contact : contacts) { if (inputString.equalsIgnoreCase(contact.getAlias())) { recipient = contact; @@ -137,8 +153,8 @@ public class ComposeMessageFragment extends Fragment { } } Singleton.getBitmessageContext(getContext()).send(identity, recipient, - subjectInput.getText().toString(), - bodyInput.getText().toString()); + subjectInput.getText().toString(), + bodyInput.getText().toString()); getActivity().finish(); return true; default: 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 b67cee5..6afb2d0 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java @@ -44,6 +44,7 @@ import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.ports.MessageRepository; import static android.text.util.Linkify.WEB_URLS; +import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_CONTENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT; @@ -100,7 +101,7 @@ public class MessageDetailFragment extends Fragment { ((TextView) rootView.findViewById(R.id.subject)).setText(item.getSubject()); BitmessageAddress sender = item.getFrom(); ((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon - (sender)); + (sender)); ((TextView) rootView.findViewById(R.id.sender)).setText(sender.toString()); if (item.getTo() != null) { ((TextView) rootView.findViewById(R.id.recipient)).setText(item.getTo().toString()); @@ -112,11 +113,11 @@ public class MessageDetailFragment extends Fragment { Linkify.addLinks(messageBody, WEB_URLS); Linkify.addLinks(messageBody, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null, - new TransformFilter() { - public final String transformUrl(final Matcher match, String url) { - return match.group(); - } - }); + new TransformFilter() { + public final String transformUrl(final Matcher match, String url) { + return match.group(); + } + }); messageBody.setLinksClickable(true); messageBody.setTextIsSelectable(true); @@ -146,7 +147,7 @@ public class MessageDetailFragment extends Fragment { Drawables.addIcon(getActivity(), menu, R.id.reply, GoogleMaterial.Icon.gmd_reply); Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete); Drawables.addIcon(getActivity(), menu, R.id.mark_unread, GoogleMaterial.Icon - .gmd_markunread); + .gmd_markunread); Drawables.addIcon(getActivity(), menu, R.id.archive, GoogleMaterial.Icon.gmd_archive); super.onCreateOptionsMenu(menu, inflater); @@ -158,17 +159,20 @@ public class MessageDetailFragment extends Fragment { switch (menuItem.getItemId()) { case R.id.reply: Intent replyIntent = new Intent(getActivity().getApplicationContext(), - ComposeMessageActivity.class); + ComposeMessageActivity.class); replyIntent.putExtra(EXTRA_RECIPIENT, item.getFrom()); replyIntent.putExtra(EXTRA_IDENTITY, item.getTo()); String prefix; if (item.getSubject().length() >= 3 && item.getSubject().substring(0, 3) - .equalsIgnoreCase("RE:")) { + .equalsIgnoreCase("RE:")) { prefix = ""; } else { prefix = "RE: "; } replyIntent.putExtra(EXTRA_SUBJECT, prefix + item.getSubject()); + replyIntent.putExtra(EXTRA_CONTENT, + "\n\n------------------------------------------------------\n" + + item.getText()); startActivity(replyIntent); return true; case R.id.delete: diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidAddressRepository.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidAddressRepository.java index 25c9b2a..4eb3374 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidAddressRepository.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidAddressRepository.java @@ -119,37 +119,36 @@ public class AndroidAddressRepository implements AddressRepository { // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - COLUMN_ADDRESS, - COLUMN_ALIAS, - COLUMN_PUBLIC_KEY, - COLUMN_PRIVATE_KEY, - COLUMN_SUBSCRIBED, - COLUMN_CHAN + COLUMN_ADDRESS, + COLUMN_ALIAS, + COLUMN_PUBLIC_KEY, + COLUMN_PRIVATE_KEY, + COLUMN_SUBSCRIBED, + COLUMN_CHAN }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - TABLE_NAME, projection, - where, - null, null, null, null + TABLE_NAME, projection, + where, + null, null, null, null )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { BitmessageAddress address; byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); if (privateKeyBytes != null) { PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream - (privateKeyBytes)); + (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); + .getStream(), + new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, + false); if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { pubkey = new V4Pubkey((V3Pubkey) pubkey); } @@ -161,7 +160,6 @@ public class AndroidAddressRepository implements AddressRepository { address.setSubscribed(c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1); result.add(address); - c.moveToNext(); } } catch (IOException e) { LOG.error(e.getMessage(), e); @@ -184,8 +182,10 @@ public class AndroidAddressRepository implements AddressRepository { private boolean exists(BitmessageAddress address) { SQLiteDatabase db = sql.getReadableDatabase(); - try (Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address - .getAddress() + "'", null)) { + try (Cursor cursor = db.rawQuery( + "SELECT COUNT(*) FROM Address WHERE address=?", + new String[]{address.getAddress()} + )) { cursor.moveToFirst(); return cursor.getInt(0) > 0; } @@ -210,8 +210,8 @@ public class AndroidAddressRepository implements AddressRepository { values.put(COLUMN_CHAN, address.isChan()); 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=?", + new String[]{address.getAddress()}); if (update < 0) { LOG.error("Could not update address " + address); } 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 c88ca2a..8632679 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 @@ -25,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -42,6 +41,7 @@ import ch.dissem.bitmessage.utils.Encode; import static ch.dissem.apps.abit.repository.SqlHelper.join; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static ch.dissem.bitmessage.utils.UnixTime.now; +import static java.lang.String.valueOf; /** * {@link Inventory} implementation using the Android SQL API. @@ -88,21 +88,19 @@ public class AndroidInventory implements Inventory { cache.put(stream, result); String[] projection = { - COLUMN_HASH, COLUMN_EXPIRES + COLUMN_HASH, COLUMN_EXPIRES }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - TABLE_NAME, projection, - "stream = " + stream, - null, null, null, null + TABLE_NAME, projection, + "stream = " + stream, + null, null, null, null )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); long expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES)); result.put(new InventoryVector(blob), expires); - c.moveToNext(); } } LOG.info("Stream #" + stream + " inventory size: " + result.size()); @@ -126,18 +124,17 @@ public class AndroidInventory implements Inventory { // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - COLUMN_VERSION, - COLUMN_DATA + COLUMN_VERSION, + COLUMN_DATA }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - TABLE_NAME, projection, - "hash = X'" + vector + "'", - null, null, null, null + TABLE_NAME, projection, + "hash = X'" + vector + "'", + null, null, null, null )) { - c.moveToFirst(); - if (c.isAfterLast()) { + if (!c.moveToFirst()) { LOG.info("Object requested that we don't have. IV: " + vector); return null; } @@ -153,8 +150,8 @@ public class AndroidInventory implements Inventory { // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - COLUMN_VERSION, - COLUMN_DATA + COLUMN_VERSION, + COLUMN_DATA }; StringBuilder where = new StringBuilder("1=1"); if (stream > 0) { @@ -170,17 +167,15 @@ public class AndroidInventory implements Inventory { SQLiteDatabase db = sql.getReadableDatabase(); List result = new LinkedList<>(); try (Cursor c = db.query( - TABLE_NAME, projection, - where.toString(), - null, null, null, null + TABLE_NAME, projection, + where.toString(), + null, null, null, null )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { 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(); + blob.length)); } } return result; @@ -211,8 +206,6 @@ public class AndroidInventory implements Inventory { getCache(object.getStream()).put(iv, object.getExpiresTime()); } catch (SQLiteConstraintException e) { LOG.trace(e.getMessage(), e); - } catch (IOException e) { - LOG.error(e.getMessage(), e); } } @@ -225,7 +218,7 @@ public class AndroidInventory implements Inventory { public void cleanup() { long fiveMinutesAgo = now() - 5 * MINUTE; SQLiteDatabase db = sql.getWritableDatabase(); - db.delete(TABLE_NAME, "expires < " + fiveMinutesAgo, null); + db.delete(TABLE_NAME, "expires < ?", new String[]{valueOf(fiveMinutesAgo)}); for (Map c : cache.values()) { Iterator> iterator = c.entrySet().iterator(); diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java index 5a1765e..f430f66 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java @@ -41,6 +41,8 @@ import ch.dissem.bitmessage.ports.AbstractMessageRepository; import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.utils.Encode; +import static java.lang.String.valueOf; + /** * {@link MessageRepository} implementation using the Android SQL API. */ @@ -87,23 +89,21 @@ public class AndroidMessageRepository extends AbstractMessageRepository { // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - LBL_COLUMN_ID, - LBL_COLUMN_LABEL, - LBL_COLUMN_TYPE, - LBL_COLUMN_COLOR + LBL_COLUMN_ID, + LBL_COLUMN_LABEL, + LBL_COLUMN_TYPE, + LBL_COLUMN_COLOR }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - LBL_TABLE_NAME, projection, - where, - null, null, null, - LBL_COLUMN_ORDER + LBL_TABLE_NAME, projection, + where, + null, null, null, + LBL_COLUMN_ORDER )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { result.add(getLabel(c)); - c.moveToNext(); } } return result; @@ -140,26 +140,34 @@ public class AndroidMessageRepository extends AbstractMessageRepository { } } Label label = new Label( - text, - type, - c.getInt(c.getColumnIndex(LBL_COLUMN_COLOR))); + text, + type, + c.getInt(c.getColumnIndex(LBL_COLUMN_COLOR))); label.setId(c.getLong(c.getColumnIndex(LBL_COLUMN_ID))); return label; } @Override public int countUnread(Label label) { + String[] args; 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=?) AND "; + args = new String[]{ + label.getId().toString(), + Label.Type.UNREAD.name() + }; } else { where = ""; + args = new String[]{ + Label.Type.UNREAD.name() + }; } SQLiteDatabase db = sql.getReadableDatabase(); return (int) DatabaseUtils.queryNumEntries(db, TABLE_NAME, - where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + - "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))" + where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + + "SELECT id FROM Label WHERE type=?))", + args ); } @@ -169,48 +177,47 @@ public class AndroidMessageRepository extends AbstractMessageRepository { // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - COLUMN_ID, - COLUMN_IV, - COLUMN_TYPE, - COLUMN_SENDER, - COLUMN_RECIPIENT, - COLUMN_DATA, - COLUMN_ACK_DATA, - COLUMN_SENT, - COLUMN_RECEIVED, - COLUMN_STATUS, - COLUMN_TTL, - COLUMN_RETRIES, - COLUMN_NEXT_TRY + COLUMN_ID, + COLUMN_IV, + COLUMN_TYPE, + COLUMN_SENDER, + COLUMN_RECIPIENT, + COLUMN_DATA, + COLUMN_ACK_DATA, + COLUMN_SENT, + COLUMN_RECEIVED, + COLUMN_STATUS, + COLUMN_TTL, + COLUMN_RETRIES, + COLUMN_NEXT_TRY }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - TABLE_NAME, projection, - where, - null, null, null, - COLUMN_RECEIVED + " DESC" + TABLE_NAME, projection, + where, + null, null, null, + COLUMN_RECEIVED + " DESC" )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { 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))); + (COLUMN_TYPE))); Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new - ByteArrayInputStream(data)); + ByteArrayInputStream(data)); long id = c.getLong(c.getColumnIndex(COLUMN_ID)); builder.id(id); builder.IV(new InventoryVector(iv)); builder.from(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex - (COLUMN_SENDER)))); + (COLUMN_SENDER)))); builder.to(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex - (COLUMN_RECIPIENT)))); + (COLUMN_RECIPIENT)))); builder.ackData(c.getBlob(c.getColumnIndex(COLUMN_ACK_DATA))); 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)))); + (COLUMN_STATUS)))); builder.ttl(c.getLong(c.getColumnIndex(COLUMN_TTL))); builder.retries(c.getInt(c.getColumnIndex(COLUMN_RETRIES))); int nextTryColumn = c.getColumnIndex(COLUMN_NEXT_TRY); @@ -219,7 +226,6 @@ public class AndroidMessageRepository extends AbstractMessageRepository { } builder.labels(findLabels(id)); result.add(builder.build()); - c.moveToNext(); } } catch (IOException e) { LOG.error(e.getMessage(), e); @@ -240,7 +246,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository { // save from address if necessary if (message.getId() == null) { BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message - .getFrom().getAddress()); + .getFrom().getAddress()); if (savedAddress == null || savedAddress.getPrivateKey() == null) { if (savedAddress != null && savedAddress.getAlias() != null) { message.getFrom().setAlias(savedAddress.getAlias()); @@ -257,7 +263,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository { } // remove existing labels - db.delete(JOIN_TABLE_NAME, "message_id=" + message.getId(), null); + db.delete(JOIN_TABLE_NAME, "message_id=?", new String[]{valueOf(message.getId())}); // save labels ContentValues values = new ContentValues(); @@ -279,15 +285,19 @@ public class AndroidMessageRepository extends AbstractMessageRepository { private void insert(SQLiteDatabase db, Plaintext message) throws IOException { ContentValues values = new ContentValues(); values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message - .getInventoryVector().getHash()); + .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()); values.put(COLUMN_DATA, Encode.bytes(message)); + values.put(COLUMN_ACK_DATA, message.getAckData()); 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()); + values.put(COLUMN_TTL, message.getTTL()); + values.put(COLUMN_RETRIES, message.getRetries()); + values.put(COLUMN_NEXT_TRY, message.getNextTry()); long id = db.insertOrThrow(TABLE_NAME, null, values); message.setId(id); } @@ -295,15 +305,19 @@ public class AndroidMessageRepository extends AbstractMessageRepository { private void update(SQLiteDatabase db, Plaintext message) throws IOException { ContentValues values = new ContentValues(); values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message - .getInventoryVector().getHash()); + .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()); values.put(COLUMN_DATA, Encode.bytes(message)); + values.put(COLUMN_ACK_DATA, message.getAckData()); 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()); + values.put(COLUMN_TTL, message.getTTL()); + values.put(COLUMN_RETRIES, message.getRetries()); + values.put(COLUMN_NEXT_TRY, message.getNextTry()); db.update(TABLE_NAME, values, "id = " + message.getId(), null); } diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java new file mode 100644 index 0000000..b20c173 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java @@ -0,0 +1,187 @@ +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 android.database.sqlite.SQLiteDoneException; +import android.database.sqlite.SQLiteStatement; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; +import ch.dissem.bitmessage.exception.ApplicationException; +import ch.dissem.bitmessage.ports.NodeRegistry; +import ch.dissem.bitmessage.utils.Collections; +import ch.dissem.bitmessage.utils.SqlStrings; + +import static ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes; +import static ch.dissem.bitmessage.utils.Strings.hex; +import static ch.dissem.bitmessage.utils.UnixTime.DAY; +import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; +import static ch.dissem.bitmessage.utils.UnixTime.now; +import static java.lang.String.valueOf; + +/** + * @author Christian Basler + */ +public class AndroidNodeRegistry implements NodeRegistry { + private static final Logger LOG = LoggerFactory.getLogger(AndroidInventory.class); + + private static final String TABLE_NAME = "Node"; + private static final String COLUMN_STREAM = "stream"; + private static final String COLUMN_ADDRESS = "address"; + private static final String COLUMN_PORT = "port"; + private static final String COLUMN_SERVICES = "services"; + private static final String COLUMN_TIME = "time"; + + private final ThreadLocal loadExistingStatement = new ThreadLocal<>(); + + private final SqlHelper sql; + private Map> stableNodes; + + public AndroidNodeRegistry(SqlHelper sql) { + this.sql = sql; + cleanUp(); + } + + private void cleanUp() { + SQLiteDatabase db = sql.getWritableDatabase(); + db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now(-28 * DAY))}); + } + + private Long loadExistingTime(NetworkAddress node) { + SQLiteStatement statement = loadExistingStatement.get(); + if (statement == null) { + statement = sql.getWritableDatabase().compileStatement( + "SELECT " + COLUMN_TIME + + " FROM " + TABLE_NAME + + " WHERE stream=? AND address=? AND port=?" + ); + loadExistingStatement.set(statement); + } + statement.bindLong(1, node.getStream()); + statement.bindBlob(2, node.getIPv6()); + statement.bindLong(3, node.getPort()); + try { + return statement.simpleQueryForLong(); + } catch (SQLiteDoneException e) { + return null; + } + } + + @Override + public List getKnownAddresses(int limit, long... streams) { + String[] projection = { + COLUMN_STREAM, + COLUMN_ADDRESS, + COLUMN_PORT, + COLUMN_SERVICES, + COLUMN_TIME + }; + + List result = new LinkedList<>(); + SQLiteDatabase db = sql.getReadableDatabase(); + try (Cursor c = db.query( + TABLE_NAME, projection, + "stream IN (?)", + new String[]{SqlStrings.join(streams).toString()}, + null, null, + "time DESC", + valueOf(limit) + )) { + while (c.moveToNext()) { + result.add( + new NetworkAddress.Builder() + .stream(c.getLong(c.getColumnIndex(COLUMN_STREAM))) + .ipv6(c.getBlob(c.getColumnIndex(COLUMN_ADDRESS))) + .port(c.getInt(c.getColumnIndex(COLUMN_PORT))) + .services(c.getLong(c.getColumnIndex(COLUMN_SERVICES))) + .time(c.getLong(c.getColumnIndex(COLUMN_TIME))) + .build() + ); + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); + throw new ApplicationException(e); + } + if (result.isEmpty()) { + synchronized (this) { + if (stableNodes == null) { + stableNodes = loadStableNodes(); + } + } + for (long stream : streams) { + Set nodes = stableNodes.get(stream); + if (nodes != null && !nodes.isEmpty()) { + result.add(Collections.selectRandom(nodes)); + } + } + } + return result; + } + + @Override + public void offerAddresses(List nodes) { + SQLiteDatabase db = sql.getWritableDatabase(); + db.beginTransaction(); + try { + cleanUp(); + for (NetworkAddress node : nodes) { + if (node.getTime() < now(+5 * MINUTE) && node.getTime() > now(-28 * DAY)) { + synchronized (this) { + Long existing = loadExistingTime(node); + if (existing == null) { + insert(node); + } else if (node.getTime() > existing) { + update(node); + } + } + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void insert(NetworkAddress node) { + try { + SQLiteDatabase db = sql.getWritableDatabase(); + // Create a new map of values, where column names are the keys + ContentValues values = new ContentValues(); + values.put(COLUMN_STREAM, node.getStream()); + values.put(COLUMN_ADDRESS, node.getIPv6()); + values.put(COLUMN_PORT, node.getPort()); + values.put(COLUMN_SERVICES, node.getServices()); + values.put(COLUMN_TIME, node.getTime()); + + db.insertOrThrow(TABLE_NAME, null, values); + } catch (SQLiteConstraintException e) { + LOG.trace(e.getMessage(), e); + } + } + + private void update(NetworkAddress node) { + try { + SQLiteDatabase db = sql.getWritableDatabase(); + // Create a new map of values, where column names are the keys + ContentValues values = new ContentValues(); + values.put(COLUMN_SERVICES, node.getServices()); + values.put(COLUMN_TIME, node.getTime()); + + db.update(TABLE_NAME, values, + "stream=" + node.getStream() + " AND address=X'" + hex(node.getIPv6()) + "' AND " + + "port=" + node.getPort(), + null); + } catch (SQLiteConstraintException e) { + LOG.trace(e.getMessage(), e); + } + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidProofOfWorkRepository.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidProofOfWorkRepository.java index 5fccc5d..d27a821 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidProofOfWorkRepository.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidProofOfWorkRepository.java @@ -25,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -37,12 +36,13 @@ import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Strings; import static ch.dissem.bitmessage.utils.Singleton.cryptography; +import static ch.dissem.bitmessage.utils.Strings.hex; /** * @author Christian Basler */ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, InternalContext - .ContextHolder { + .ContextHolder { private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class); private static final String TABLE_NAME = "POW"; @@ -71,46 +71,45 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte // 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, - COLUMN_EXPIRATION_TIME, - COLUMN_MESSAGE_ID + COLUMN_DATA, + COLUMN_VERSION, + COLUMN_NONCE_TRIALS_PER_BYTE, + COLUMN_EXTRA_BYTES, + COLUMN_EXPIRATION_TIME, + COLUMN_MESSAGE_ID }; SQLiteDatabase db = sql.getReadableDatabase(); try (Cursor c = db.query( - TABLE_NAME, projection, - "initial_hash = X'" + Strings.hex(initialHash) + "'", - null, null, null, null + TABLE_NAME, projection, + "initial_hash=X'" + hex(initialHash) + "'", + null, null, null, null )) { - c.moveToFirst(); - if (!c.isAfterLast()) { + if (c.moveToFirst()) { int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) { 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)) + Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob + .length), + c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), + c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)) ); } else { 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)), - c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)), - bmc.getMessageRepository().getMessage( - c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID))) + Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob + .length), + c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), + c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)), + c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)), + bmc.getMessageRepository().getMessage( + c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID))) ); } } } throw new RuntimeException("Object requested that we don't have. Initial hash: " + - Strings.hex(initialHash)); + hex(initialHash)); } @Override @@ -118,20 +117,18 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { - COLUMN_INITIAL_HASH + COLUMN_INITIAL_HASH }; SQLiteDatabase db = sql.getReadableDatabase(); List result = new LinkedList<>(); try (Cursor c = db.query( - TABLE_NAME, projection, - null, null, null, null, null + TABLE_NAME, projection, + null, null, null, null, null )) { - c.moveToFirst(); - while (!c.isAfterLast()) { + while (c.moveToNext()) { byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH)); result.add(initialHash); - c.moveToNext(); } } return result; @@ -156,8 +153,6 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte db.insertOrThrow(TABLE_NAME, null, values); } catch (SQLiteConstraintException e) { LOG.trace(e.getMessage(), e); - } catch (IOException e) { - LOG.error(e.getMessage(), e); } } @@ -169,8 +164,10 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte @Override public void removeObject(byte[] initialHash) { SQLiteDatabase db = sql.getWritableDatabase(); - db.delete(TABLE_NAME, - "initial_hash = X'" + Strings.hex(initialHash) + "'", - null); + db.delete( + TABLE_NAME, + "initial_hash=X'" + hex(initialHash) + "'", + null + ); } } diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java b/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java index 9a0f7ec..87a2bc2 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java @@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; + import ch.dissem.apps.abit.util.Assets; /** @@ -26,7 +27,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 = 4; + public static final int DATABASE_VERSION = 5; public static final String DATABASE_NAME = "jabit.db"; protected final Context ctx; @@ -38,7 +39,7 @@ public class SqlHelper extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { - onUpgrade(db, 0, 2); + onUpgrade(db, 0, DATABASE_VERSION); } @Override @@ -56,8 +57,11 @@ public class SqlHelper extends SQLiteOpenHelper { case 3: executeMigration(db, "V3.1__Update_table_POW"); executeMigration(db, "V3.2__Update_table_message"); + case 4: + executeMigration(db, "V3.3__Create_table_node"); default: - // Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. + // Nothing to do. Let's assume we won't upgrade from a version that's newer than + // DATABASE_VERSION. } } 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 178c8bb..1bc0f77 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 @@ -28,16 +28,17 @@ 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.AndroidNodeRegistry; 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.networking.nio.NioNetworkHandler; import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.ProofOfWorkRepository; +import ch.dissem.bitmessage.utils.TTL; import static ch.dissem.bitmessage.utils.UnixTime.DAY; @@ -58,6 +59,7 @@ public class Singleton { final Context ctx = context.getApplicationContext(); SqlHelper sqlHelper = new SqlHelper(ctx); powRepo = new AndroidProofOfWorkRepository(sqlHelper); + TTL.pubkey(2 * DAY); bitmessageContext = new BitmessageContext.Builder() .proofOfWorkEngine(new SwitchingProofOfWorkEngine( ctx, Constants.PREFERENCE_SERVER_POW, @@ -65,15 +67,14 @@ public class Singleton { new ServicePowEngine(ctx) )) .cryptography(new AndroidCryptography()) - .nodeRegistry(new MemoryNodeRegistry()) + .nodeRegistry(new AndroidNodeRegistry(sqlHelper)) .inventory(new AndroidInventory(sqlHelper)) .addressRepo(new AndroidAddressRepository(sqlHelper)) .messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) .powRepo(powRepo) - .networkHandler(new DefaultNetworkHandler()) + .networkHandler(new NioNetworkHandler()) .listener(getMessageListener(ctx)) .doNotSendPubkeyOnIdentityCreation() - .pubkeyTTL(2 * DAY) .build(); } } diff --git a/build.gradle b/build.gradle index e8ad5dc..de60887 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From c8a0301402abafcc0a4556d066be1b7254c15a02 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 16 Sep 2016 17:35:24 +0200 Subject: [PATCH 2/3] GUI, layout and style improvements, updated dependencies --- app/build.gradle | 15 +- .../apps/abit/AddressDetailActivity.java | 31 +- .../ch/dissem/apps/abit/DetailActivity.java | 52 ++ .../ch/dissem/apps/abit/MainActivity.java | 474 +++++++++--------- .../apps/abit/MessageDetailActivity.java | 31 +- .../ch/dissem/apps/abit/SettingsActivity.java | 12 +- .../ch/dissem/apps/abit/StatusActivity.java | 9 + .../apps/abit/listener/WifiReceiver.java | 19 +- .../ch/dissem/apps/abit/util/Preferences.java | 2 +- app/src/main/res/values-v21/styles.xml | 24 - app/src/main/res/values/colors.xml | 53 +- app/src/main/res/values/styles.xml | 18 +- 12 files changed, 370 insertions(+), 370 deletions(-) create mode 100644 app/src/main/java/ch/dissem/apps/abit/DetailActivity.java delete mode 100644 app/src/main/res/values-v21/styles.xml diff --git a/app/build.gradle b/app/build.gradle index 3ba3317..f2ecf7f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,20 +44,23 @@ dependencies { compile 'org.slf4j:slf4j-android:1.7.12' - compile('com.mikepenz:materialdrawer:3.1.0@aar') { + compile 'com.mikepenz:materialize:1.0.0@aar' + compile('com.mikepenz:materialdrawer:5.6.0@aar') { transitive = true } - compile('com.mikepenz:aboutlibraries:5.3.4@aar') { + compile('com.mikepenz:aboutlibraries:5.8.1@aar') { transitive = true } compile 'com.mikepenz:iconics:1.6.2@aar' - compile 'com.mikepenz:community-material-typeface:1.1.71@aar' + compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar' compile 'com.journeyapps:zxing-android-embedded:3.1.0@aar' compile 'com.google.zxing:core:3.2.0' compile 'io.github.yavski:fab-speed-dial:1.0.2' - - compile 'com.github.amlcurran.showcaseview:library:5.4.0' + compile 'com.github.amlcurran.showcaseview:library:5.4.3' + compile ('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.9.3@aar'){ + transitive=true + } testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' @@ -72,4 +75,4 @@ android { lintOptions { abortOnError false } -} \ No newline at end of file +} diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java index 077a5d4..a00e613 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java @@ -16,12 +16,7 @@ package ch.dissem.apps.abit; -import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; /** @@ -33,18 +28,11 @@ import android.view.MenuItem; * This activity is mostly just a 'shell' activity containing nothing * more than a {@link AddressDetailFragment}. */ -public class AddressDetailActivity extends AppCompatActivity { +public class AddressDetailActivity extends DetailActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.scrolling_toolbar_layout); - - final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - // Show the Up button in the action bar. - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); // savedInstanceState is non-null when there is fragment state // saved from previous configurations of this activity @@ -68,21 +56,4 @@ public class AddressDetailActivity extends AppCompatActivity { .commit(); } } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == android.R.id.home) { - // This ID represents the Home or Up button. In the case of this - // activity, the Up button is shown. Use NavUtils to allow users - // to navigate up one level in the application structure. For - // more details, see the Navigation pattern on Android Design: - // - // http://developer.android.com/design/patterns/navigation.html#up-vs-back - // - NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); - return true; - } - return super.onOptionsItemSelected(item); - } } diff --git a/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java new file mode 100644 index 0000000..fb94ef2 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java @@ -0,0 +1,52 @@ +package ch.dissem.apps.abit; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.NavUtils; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; + +import com.mikepenz.materialize.MaterializeBuilder; + +/** + * @author Christian Basler + */ +public class DetailActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.scrolling_toolbar_layout); + + final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + // Show the Up button in the action bar. + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + new MaterializeBuilder() + .withActivity(this) + .withStatusBarColorRes(R.color.colorPrimaryDark) + .withTranslucentStatusBarProgrammatically(true) + .withStatusBarPadding(true) + .build(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + // This ID represents the Home or Up button. In the case of this + // activity, the Up button is shown. Use NavUtils to allow users + // to navigate up one level in the application structure. For + // more details, see the Navigation pattern on Android Design: + // + // http://developer.android.com/design/patterns/navigation.html#up-vs-back + // + NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java index b1e9ded..b8825c8 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java @@ -32,7 +32,6 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.CompoundButton; import android.widget.RelativeLayout; import android.widget.TextView; @@ -43,10 +42,11 @@ import com.github.amlcurran.showcaseview.targets.Target; import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.iconics.IconicsDrawable; +import com.mikepenz.materialdrawer.AccountHeader; +import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; -import com.mikepenz.materialdrawer.accountswitcher.AccountHeader; -import com.mikepenz.materialdrawer.accountswitcher.AccountHeaderBuilder; +import com.mikepenz.materialdrawer.interfaces.OnCheckedChangeListener; import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.ProfileDrawerItem; @@ -55,7 +55,6 @@ 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; @@ -100,7 +99,7 @@ import static ch.dissem.apps.abit.service.BitmessageService.isRunning; *

*/ public class MainActivity extends AppCompatActivity - implements ListSelectionListener, ActionBarListener { + implements ListSelectionListener, ActionBarListener { public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; @@ -109,7 +108,7 @@ public class MainActivity extends AppCompatActivity private static final int MANAGE_IDENTITY = 2; private static final int ADD_CHAN = 3; - public static WeakReference instance; + private static WeakReference instance; /** * Whether or not the activity is in two-pane mode, i.e. running on a tablet @@ -157,7 +156,7 @@ public class MainActivity extends AppCompatActivity MessageListFragment listFragment = new MessageListFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment) - .commit(); + .commit(); if (findViewById(R.id.message_detail_container) != null) { // The detail container view will be present only in the @@ -187,42 +186,42 @@ public class MainActivity extends AppCompatActivity } if (drawer.isDrawerOpen()) { RelativeLayout.LayoutParams lps = new RelativeLayout.LayoutParams(ViewGroup - .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT); int margin = ((Number) (getResources().getDisplayMetrics().density * 12)).intValue(); lps.setMargins(margin, margin, margin, margin); showcaseView = new ShowcaseView.Builder(this) - .withMaterialShowcase() - .setStyle(R.style.CustomShowcaseTheme) - .setContentTitle(R.string.full_node) - .setContentText(R.string.full_node_description) - .setTarget(new Target() { - @Override - public Point getPoint() { - View view = drawer.getStickyFooter(); - int[] location = new int[2]; - view.getLocationInWindow(location); - int x = location[0] + 7 * view.getWidth() / 8; - int y = location[1] + view.getHeight() / 2; - return new Point(x, y); - } + .withMaterialShowcase() + .setStyle(R.style.CustomShowcaseTheme) + .setContentTitle(R.string.full_node) + .setContentText(R.string.full_node_description) + .setTarget(new Target() { + @Override + public Point getPoint() { + View view = drawer.getStickyFooter(); + int[] location = new int[2]; + view.getLocationInWindow(location); + int x = location[0] + 7 * view.getWidth() / 8; + int y = location[1] + view.getHeight() / 2; + return new Point(x, y); } - ) - .replaceEndButton(R.layout.showcase_button) - .hideOnTouchOutside() - .build(); + } + ) + .replaceEndButton(R.layout.showcase_button) + .hideOnTouchOutside() + .build(); showcaseView.setButtonPosition(lps); } } private void changeList(AbstractItemListFragment listFragment) { getSupportFragmentManager() - .beginTransaction() - .replace(R.id.item_list, listFragment) - .addToBackStack(null) - .commit(); + .beginTransaction() + .replace(R.id.item_list, listFragment) + .addToBackStack(null) + .commit(); if (twoPane) { // In two-pane mode, list items should be given the @@ -236,90 +235,91 @@ public class MainActivity extends AppCompatActivity for (BitmessageAddress identity : bmc.addresses().getIdentities()) { LOG.info("Adding identity " + identity.getAddress()); profiles.add(new ProfileDrawerItem() - .withIcon(new Identicon(identity)) - .withName(identity.toString()) - .withNameShown(true) - .withEmail(identity.getAddress()) - .withTag(identity) + .withIcon(new Identicon(identity)) + .withName(identity.toString()) + .withNameShown(true) + .withEmail(identity.getAddress()) + .withTag(identity) ); } if (profiles.isEmpty()) { // Create an initial identity BitmessageAddress identity = Singleton.getIdentity(this); profiles.add(new ProfileDrawerItem() - .withIcon(new Identicon(identity)) - .withName(identity.toString()) - .withEmail(identity.getAddress()) - .withTag(identity) + .withIcon(new Identicon(identity)) + .withName(identity.toString()) + .withEmail(identity.getAddress()) + .withTag(identity) ); } profiles.add(new ProfileSettingDrawerItem() - .withName(getString(R.string.add_identity)) - .withDescription(getString(R.string.add_identity_summary)) - .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) - .actionBar() - .paddingDp(5) - .colorRes(R.color.icons)) - .withIdentifier(ADD_IDENTITY) + .withName(getString(R.string.add_identity)) + .withDescription(getString(R.string.add_identity_summary)) + .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) + .actionBar() + .paddingDp(5) + .colorRes(R.color.icons)) + .withIdentifier(ADD_IDENTITY) ); profiles.add(new ProfileSettingDrawerItem() - .withName(getString(R.string.add_chan)) - .withDescription(getString(R.string.add_chan_summary)) - .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) - .actionBar() - .paddingDp(5) - .colorRes(R.color.icons)) - .withIdentifier(ADD_CHAN) + .withName(getString(R.string.add_chan)) + .withDescription(getString(R.string.add_chan_summary)) + .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) + .actionBar() + .paddingDp(5) + .colorRes(R.color.icons)) + .withIdentifier(ADD_CHAN) ); profiles.add(new ProfileSettingDrawerItem() - .withName(getString(R.string.manage_identity)) - .withIcon(GoogleMaterial.Icon.gmd_settings) - .withIdentifier(MANAGE_IDENTITY) + .withName(getString(R.string.manage_identity)) + .withIcon(GoogleMaterial.Icon.gmd_settings) + .withIdentifier(MANAGE_IDENTITY) ); // Create the AccountHeader accountHeader = new AccountHeaderBuilder() - .withActivity(this) - .withHeaderBackground(R.drawable.header) - .withProfiles(profiles) - .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { - @Override - public boolean onProfileChanged(View view, IProfile profile, boolean - currentProfile) { - switch (profile.getIdentifier()) { - case ADD_IDENTITY: - addIdentityDialog(); - break; - case ADD_CHAN: - addChanDialog(); - break; - case MANAGE_IDENTITY: - Intent show = new Intent(MainActivity.this, - AddressDetailActivity.class); - show.putExtra(AddressDetailFragment.ARG_ITEM, - Singleton.getIdentity(getApplicationContext())); - startActivity(show); - break; - default: - if (profile instanceof ProfileDrawerItem) { - Object tag = ((ProfileDrawerItem) profile).getTag(); - if (tag instanceof BitmessageAddress) { - Singleton.setIdentity((BitmessageAddress) tag); - } + .withActivity(this) + .withHeaderBackground(R.drawable.header) + .withProfiles(profiles) + .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { + @Override + public boolean onProfileChanged(View view, IProfile profile, boolean + currentProfile) { + switch ((int) profile.getIdentifier()) { + case ADD_IDENTITY: + addIdentityDialog(); + break; + case ADD_CHAN: + addChanDialog(); + break; + case MANAGE_IDENTITY: + Intent show = new Intent(MainActivity.this, + AddressDetailActivity.class); + show.putExtra(AddressDetailFragment.ARG_ITEM, + Singleton.getIdentity(getApplicationContext())); + startActivity(show); + break; + default: + if (profile instanceof ProfileDrawerItem) { + Object tag = ((ProfileDrawerItem) profile).getTag(); + if (tag instanceof BitmessageAddress) { + Singleton.setIdentity((BitmessageAddress) tag); } - } - // false if it should close the drawer - return false; + } } - }) - .build(); + // false if it should close the drawer + return false; + } + }) + .build(); if (profiles.size() > 2) { // There's always the add and manage identity items accountHeader.setActiveProfile(profiles.get(0), true); } ArrayList drawerItems = new ArrayList<>(); for (Label label : labels) { - PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag - (label); + PrimaryDrawerItem item = new PrimaryDrawerItem() + .withName(label.toString()) + .withTag(label); if (label.getType() == null) { item.withIcon(CommunityMaterial.Icon.cmd_label); } else { @@ -349,146 +349,144 @@ public class MainActivity extends AppCompatActivity drawerItems.add(item); } drawerItems.add(new PrimaryDrawerItem() - .withName(R.string.archive) - .withTag(null) - .withIcon(CommunityMaterial.Icon.cmd_archive) + .withName(R.string.archive) + .withTag(null) + .withIcon(CommunityMaterial.Icon.cmd_archive) ); drawerItems.add(new DividerDrawerItem()); drawerItems.add(new PrimaryDrawerItem() - .withName(R.string.contacts_and_subscriptions) - .withIcon(GoogleMaterial.Icon.gmd_contacts)); + .withName(R.string.contacts_and_subscriptions) + .withIcon(GoogleMaterial.Icon.gmd_contacts)); drawerItems.add(new PrimaryDrawerItem() - .withName(R.string.settings) - .withIcon(GoogleMaterial.Icon.gmd_settings)); + .withName(R.string.settings) + .withIcon(GoogleMaterial.Icon.gmd_settings)); drawer = new DrawerBuilder() - .withActivity(this) - .withToolbar(toolbar) - .withAccountHeader(accountHeader) - .withDrawerItems(drawerItems) - .addStickyDrawerItems( - new SwitchDrawerItem() - .withName(R.string.full_node) - .withIcon(CommunityMaterial.Icon.cmd_cloud_outline) - .withChecked(isRunning()) - .withOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(IDrawerItem drawerItem, - CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - checkAndStartNode(buttonView); - } else { - service.shutdownNode(); - } - } - }) - ) - .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(); - showSelectedLabel(); - return false; - } else if (item instanceof Nameable) { - Nameable ni = (Nameable) item; - switch (ni.getNameRes()) { - case R.string.contacts_and_subscriptions: - if (!(getSupportFragmentManager().findFragmentById(R.id - .item_list) instanceof AddressListFragment)) { - changeList(new AddressListFragment()); - } else { - ((AddressListFragment) getSupportFragmentManager() - .findFragmentById(R.id.item_list)).updateList(); - } - - break; - case R.string.settings: - startActivity(new Intent(MainActivity.this, SettingsActivity - .class)); - break; - case R.string.archive: - selectedLabel = null; - showSelectedLabel(); - break; - case R.string.full_node: - return true; + .withActivity(this) + .withToolbar(toolbar) + .withAccountHeader(accountHeader) + .withDrawerItems(drawerItems) + .addStickyDrawerItems( + new SwitchDrawerItem() + .withName(R.string.full_node) + .withIcon(CommunityMaterial.Icon.cmd_cloud_outline) + .withChecked(isRunning()) + .withOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(IDrawerItem drawerItem, + CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + checkAndStartNode(buttonView); + } else { + service.shutdownNode(); } } + }) + ) + .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { + @Override + public boolean onItemClick(View view, int position, IDrawerItem item) { + if (item.getTag() instanceof Label) { + selectedLabel = (Label) item.getTag(); + showSelectedLabel(); return false; + } else if (item instanceof Nameable) { + Nameable ni = (Nameable) item; + switch (ni.getName().getTextRes()) { + case R.string.contacts_and_subscriptions: + if (!(getSupportFragmentManager().findFragmentById(R.id + .item_list) instanceof AddressListFragment)) { + changeList(new AddressListFragment()); + } else { + ((AddressListFragment) getSupportFragmentManager() + .findFragmentById(R.id.item_list)).updateList(); + } + break; + case R.string.settings: + startActivity(new Intent(MainActivity.this, SettingsActivity + .class)); + break; + case R.string.archive: + selectedLabel = null; + showSelectedLabel(); + break; + case R.string.full_node: + return true; + } } - }) - .withShowDrawerOnFirstLaunch(true) - .build(); + return false; + } + }) + .withShowDrawerOnFirstLaunch(true) + .build(); } private void addIdentityDialog() { new AlertDialog.Builder(MainActivity.this) - .setMessage(R.string.add_identity_warning) - .setPositiveButton(android.R.string.yes, new - DialogInterface.OnClickListener() { + .setMessage(R.string.add_identity_warning) + .setPositiveButton(android.R.string.yes, new + DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + Toast.makeText(MainActivity.this, + R.string.toast_long_running_operation, + Toast.LENGTH_SHORT).show(); + new AsyncTask() { @Override - public void onClick(DialogInterface dialog, - int which) { - Toast.makeText(MainActivity.this, - R.string.toast_long_running_operation, - Toast.LENGTH_SHORT).show(); - new AsyncTask() { - @Override - protected BitmessageAddress doInBackground(Void... args) { - return bmc.createIdentity(false, Pubkey.Feature.DOES_ACK); - } - - @Override - protected void onPostExecute(BitmessageAddress chan) { - Toast.makeText(MainActivity.this, - R.string.toast_identity_created, - Toast.LENGTH_SHORT).show(); - addIdentityEntry(chan); - } - }.execute(); + protected BitmessageAddress doInBackground(Void... args) { + return bmc.createIdentity(false, Pubkey.Feature.DOES_ACK); } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + + @Override + protected void onPostExecute(BitmessageAddress chan) { + Toast.makeText(MainActivity.this, + R.string.toast_identity_created, + Toast.LENGTH_SHORT).show(); + addIdentityEntry(chan); + } + }.execute(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); } private void addChanDialog() { @SuppressLint("InflateParams") final View dialogView = getLayoutInflater().inflate(R.layout.dialog_input_passphrase, null); new AlertDialog.Builder(MainActivity.this) - .setMessage(R.string.add_chan) - .setView(dialogView) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase); - Toast.makeText(MainActivity.this, R.string.toast_long_running_operation, - Toast.LENGTH_SHORT).show(); - new AsyncTask() { - @Override - protected BitmessageAddress doInBackground(String... args) { - String pass = args[0]; - BitmessageAddress chan = bmc.createChan(pass); - chan.setAlias(pass); - bmc.addresses().save(chan); - return chan; - } + .setMessage(R.string.add_chan) + .setView(dialogView) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase); + Toast.makeText(MainActivity.this, R.string.toast_long_running_operation, + Toast.LENGTH_SHORT).show(); + new AsyncTask() { + @Override + protected BitmessageAddress doInBackground(String... args) { + String pass = args[0]; + BitmessageAddress chan = bmc.createChan(pass); + chan.setAlias(pass); + bmc.addresses().save(chan); + return chan; + } - @Override - protected void onPostExecute(BitmessageAddress chan) { - Toast.makeText(MainActivity.this, - R.string.toast_chan_created, - Toast.LENGTH_SHORT).show(); - addIdentityEntry(chan); - } - }.execute(passphrase.getText().toString()); - } - }) - .setNegativeButton(R.string.cancel, null) - .show(); + @Override + protected void onPostExecute(BitmessageAddress chan) { + Toast.makeText(MainActivity.this, + R.string.toast_chan_created, + Toast.LENGTH_SHORT).show(); + addIdentityEntry(chan); + } + }.execute(passphrase.getText().toString()); + } + }) + .setNegativeButton(R.string.cancel, null) + .show(); } @Override @@ -500,18 +498,18 @@ public class MainActivity extends AppCompatActivity private void addIdentityEntry(BitmessageAddress identity) { IProfile newProfile = new - ProfileDrawerItem() - .withName(identity.toString()) - .withEmail(identity.getAddress()) - .withTag(identity); + ProfileDrawerItem() + .withName(identity.toString()) + .withEmail(identity.getAddress()) + .withTag(identity); if (accountHeader.getProfiles() != null) { // we know that there are 3 setting // elements. // Set the new profile above them ;) accountHeader.addProfile( - newProfile, accountHeader - .getProfiles().size() - - 3); + newProfile, accountHeader + .getProfiles().size() + - 3); } else { accountHeader.addProfiles(newProfile); } @@ -530,20 +528,20 @@ public class MainActivity extends AppCompatActivity service.startupNode(); } else { new AlertDialog.Builder(MainActivity.this) - .setMessage(R.string.full_node_warning) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - service.startupNode(); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - buttonView.setChecked(false); - } - }) - .show(); + .setMessage(R.string.full_node_warning) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + service.startupNode(); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + buttonView.setChecked(false); + } + }) + .show(); } } @@ -556,7 +554,7 @@ public class MainActivity extends AppCompatActivity if (unread > 0) { ((PrimaryDrawerItem) item).withBadge(String.valueOf(unread)); } else { - ((PrimaryDrawerItem) item).withBadge(null); + ((PrimaryDrawerItem) item).withBadge((String) null); } } } @@ -564,9 +562,9 @@ public class MainActivity extends AppCompatActivity private void showSelectedLabel() { if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof - MessageListFragment) { + MessageListFragment) { ((MessageListFragment) getSupportFragmentManager() - .findFragmentById(R.id.item_list)).updateList(selectedLabel); + .findFragmentById(R.id.item_list)).updateList(selectedLabel); } else { MessageListFragment listFragment = new MessageListFragment(); changeList(listFragment); @@ -593,12 +591,12 @@ public class MainActivity extends AppCompatActivity fragment = new AddressDetailFragment(); else throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + - "was " - + item.getClass().getSimpleName()); + "was " + + item.getClass().getSimpleName()); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction() - .replace(R.id.message_detail_container, fragment) - .commit(); + .replace(R.id.message_detail_container, fragment) + .commit(); } else { // In single-pane mode, simply start the detail activity // for the selected item ID. @@ -609,8 +607,8 @@ public class MainActivity extends AppCompatActivity detailIntent = new Intent(this, AddressDetailActivity.class); else throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + - "was " - + item.getClass().getSimpleName()); + "was " + + item.getClass().getSimpleName()); detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); startActivity(detailIntent); @@ -632,7 +630,7 @@ public class MainActivity extends AppCompatActivity protected void onStart() { super.onStart(); bindService(new Intent(this, BitmessageService.class), connection, Context - .BIND_AUTO_CREATE); + .BIND_AUTO_CREATE); } @Override diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.java index 7957185..dbad234 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.java @@ -1,11 +1,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.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; /** @@ -17,18 +12,11 @@ import android.view.MenuItem; * This activity is mostly just a 'shell' activity containing nothing * more than a {@link MessageDetailFragment}. */ -public class MessageDetailActivity extends AppCompatActivity { +public class MessageDetailActivity extends DetailActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.scrolling_toolbar_layout); - - final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - // Show the Up button in the action bar. - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); // savedInstanceState is non-null when there is fragment state // saved from previous configurations of this activity @@ -52,21 +40,4 @@ public class MessageDetailActivity extends AppCompatActivity { .commit(); } } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == android.R.id.home) { - // This ID represents the Home or Up button. In the case of this - // activity, the Up button is shown. Use NavUtils to allow users - // to navigate up one level in the application structure. For - // more details, see the Navigation pattern on Android Design: - // - // http://developer.android.com/design/patterns/navigation.html#up-vs-back - // - NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); - return true; - } - return super.onOptionsItemSelected(item); - } } diff --git a/app/src/main/java/ch/dissem/apps/abit/SettingsActivity.java b/app/src/main/java/ch/dissem/apps/abit/SettingsActivity.java index 6a86ef2..07f4cc7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SettingsActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/SettingsActivity.java @@ -7,22 +7,14 @@ import android.support.v7.widget.Toolbar; /** * @author Christian Basler */ -public class SettingsActivity extends AppCompatActivity { +public class SettingsActivity extends DetailActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.scrolling_toolbar_layout); - - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(false); // Display the fragment as the main content. getFragmentManager().beginTransaction() .replace(R.id.content, new SettingsFragment()) .commit(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/ch/dissem/apps/abit/StatusActivity.java b/app/src/main/java/ch/dissem/apps/abit/StatusActivity.java index 1babcf6..cefd609 100644 --- a/app/src/main/java/ch/dissem/apps/abit/StatusActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/StatusActivity.java @@ -21,6 +21,8 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.widget.TextView; +import com.mikepenz.materialize.MaterializeBuilder; + import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.BitmessageAddress; @@ -39,6 +41,13 @@ public class StatusActivity extends AppCompatActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(false); + new MaterializeBuilder() + .withActivity(this) + .withStatusBarColorRes(R.color.colorPrimaryDark) + .withTranslucentStatusBarProgrammatically(true) + .withStatusBarPadding(true) + .build(); + BitmessageContext bmc = Singleton.getBitmessageContext(this); StringBuilder status = new StringBuilder(); for (BitmessageAddress address : bmc.addresses().getIdentities()) { diff --git a/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.java b/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.java index e4ea8e0..ccd81ac 100644 --- a/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.java +++ b/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.java @@ -32,20 +32,29 @@ public class WifiReceiver extends BroadcastReceiver { if (Preferences.isWifiOnly(ctx)) { BitmessageContext bmc = Singleton.getBitmessageContext(ctx); - if (!isConnectedToWifi(ctx) && bmc.isRunning()) { + if (isConnectedToMeteredNetwork(ctx) && bmc.isRunning()) { bmc.shutdown(); } } } - public static boolean isConnectedToWifi(Context ctx) { + public static boolean isConnectedToMeteredNetwork(Context ctx) { NetworkInfo netInfo = getNetworkInfo(ctx); - return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI; + if (netInfo == null || !netInfo.isConnectedOrConnecting()) { + return false; + } + switch (netInfo.getType()){ + case ConnectivityManager.TYPE_ETHERNET: + case ConnectivityManager.TYPE_WIFI: + return false; + default: + return true; + } } private static NetworkInfo getNetworkInfo(Context ctx) { ConnectivityManager conMan = (ConnectivityManager) ctx.getSystemService(Context - .CONNECTIVITY_SERVICE); + .CONNECTIVITY_SERVICE); return conMan.getActiveNetworkInfo(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.java b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.java index a0cee3f..b7f3d97 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Preferences.java +++ b/app/src/main/java/ch/dissem/apps/abit/util/Preferences.java @@ -101,7 +101,7 @@ public class Preferences { } public static boolean isConnectionAllowed(Context ctx) { - return !isWifiOnly(ctx) || WifiReceiver.isConnectedToWifi(ctx); + return !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx); } public static boolean isWifiOnly(Context ctx) { diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 72150b4..0000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8958204..6e7e100 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,13 +1,48 @@ - #FFC107 - #FFA000 - #DEFFFFFF - #FFECB3 - #607D8B - #212121 - #727272 - #212121 - #B6B6B6 + #FFC107 + #FFA000 + #DEFFFFFF + #FFECB3 + #607D8B + #212121 + #727272 + #212121 + #B6B6B6 + + + @color/colorPrimary + @color/colorPrimaryDark + @color/colorPrimaryLight + @color/colorAccent + + + + @color/colorPrimaryDark + + @color/colorPrimaryText + @color/icons + @color/colorSecondaryText + @color/colorSecondaryText + @color/divider + + @color/primary + @color/colorPrimaryText + @color/colorPrimaryText + + + + #303030 + + #DEFFFFFF + #8AFFFFFF + #8AFFFFFF + #42FFFFFF + #1FFFFFFF + + #202020 + @color/material_drawer_primary + #FFF + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 58f503e..a299e46 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,28 +1,12 @@ - -