Asynchronously load contacts to improve responsiveness

This commit is contained in:
Christian Basler 2017-06-29 23:29:56 +02:00
parent a67560c28b
commit bf52d2f3de
17 changed files with 261 additions and 182 deletions

View File

@ -70,9 +70,6 @@ public class AddressDetailFragment extends Fragment {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM)) { if (getArguments().containsKey(ARG_ITEM)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
item = (BitmessageAddress) getArguments().getSerializable(ARG_ITEM); item = (BitmessageAddress) getArguments().getSerializable(ARG_ITEM);
} }
setHasOptionsMenu(true); setHasOptionsMenu(true);

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -32,11 +33,11 @@ import android.widget.TextView;
import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentIntegrator;
import java.util.Collections; import java.util.LinkedList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import ch.dissem.apps.abit.listener.ActionBarListener; import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
@ -47,6 +48,46 @@ import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter;
* Fragment that shows a list of all contacts, the ones we subscribed to first. * Fragment that shows a list of all contacts, the ones we subscribed to first.
*/ */
public class AddressListFragment extends AbstractItemListFragment<BitmessageAddress> { public class AddressListFragment extends AbstractItemListFragment<BitmessageAddress> {
private ArrayAdapter<BitmessageAddress> adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new ArrayAdapter<BitmessageAddress>(
getActivity(),
R.layout.subscription_row,
R.id.name,
new LinkedList<BitmessageAddress>()) {
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder v;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.subscription_row, parent, false);
v = new ViewHolder();
v.ctx = getContext();
v.avatar = (ImageView) convertView.findViewById(R.id.avatar);
v.name = (TextView) convertView.findViewById(R.id.name);
v.streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
v.subscribed = convertView.findViewById(R.id.subscribed);
convertView.setTag(v);
} else {
v = (ViewHolder) convertView.getTag();
}
BitmessageAddress item = getItem(position);
assert item != null;
v.avatar.setImageDrawable(new Identicon(item));
v.name.setText(item.toString());
v.streamNumber.setText(v.ctx.getString(R.string.stream_number, item.getStream()));
v.subscribed.setVisibility(item.isSubscribed() ? View.VISIBLE : View.INVISIBLE);
return convertView;
}
};
setListAdapter(adapter);
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -55,61 +96,26 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
} }
public void updateList() { public void updateList() {
List<BitmessageAddress> addresses = Singleton.getAddressRepository(getContext()) adapter.clear();
.getContacts(); final AndroidAddressRepository addressRepo = Singleton.getAddressRepository(getContext());
Collections.sort(addresses, new Comparator<BitmessageAddress>() { new AsyncTask<Void, BitmessageAddress, Void>() {
@Override @Override
public int compare(BitmessageAddress lhs, BitmessageAddress rhs) { protected Void doInBackground(Void... params) {
// Yields the following order: List<String> ids = addressRepo.getContactIds();
// * Subscribed addresses come first for (String id : ids) {
// * Addresses with Aliases (alphabetically) BitmessageAddress address = addressRepo.getById(id);
// * Addresses (alphabetically) publishProgress(address);
if (lhs.isSubscribed() == rhs.isSubscribed()) {
if (lhs.getAlias() != null) {
if (rhs.getAlias() != null) {
return lhs.getAlias().compareTo(rhs.getAlias());
} else {
return -1;
}
} else if (rhs.getAlias() != null) {
return 1;
} else {
return lhs.getAddress().compareTo(rhs.getAddress());
}
} }
if (lhs.isSubscribed()) { return null;
return -1; }
} else {
return 1; @Override
protected void onProgressUpdate(BitmessageAddress... values) {
for (BitmessageAddress address : values) {
adapter.add(address);
} }
} }
}); }.execute();
setListAdapter(new ArrayAdapter<BitmessageAddress>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
addresses) {
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.subscription_row, parent, false);
}
BitmessageAddress item = getItem(position);
assert item != null;
((ImageView) convertView.findViewById(R.id.avatar)).setImageDrawable(new
Identicon(item));
TextView name = (TextView) convertView.findViewById(R.id.name);
name.setText(item.toString());
TextView streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
streamNumber.setText(getContext().getString(R.string.stream_number,
item.getStream()));
convertView.findViewById(R.id.subscribed).setVisibility(item.isSubscribed() ?
View.VISIBLE : View.INVISIBLE);
return convertView;
}
});
} }
@Override @Override
@ -163,4 +169,12 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
public void updateList(Label label) { public void updateList(Label label) {
updateList(); updateList();
} }
private static class ViewHolder {
private Context ctx;
private ImageView avatar;
private TextView name;
private TextView streamNumber;
private View subscribed;
}
} }

View File

@ -52,21 +52,19 @@ public class ImportIdentitiesFragment extends Fragment {
String wifData = getArguments().getString(WIF_DATA); String wifData = getArguments().getString(WIF_DATA);
BitmessageContext bmc = Singleton.getBitmessageContext(getActivity()); BitmessageContext bmc = Singleton.getBitmessageContext(getActivity());
View view = inflater.inflate(R.layout.fragment_import_select_identities, container, false); View view = inflater.inflate(R.layout.fragment_import_select_identities, container, false);
try {
importer = new WifImporter(bmc, wifData);
adapter = new AddressSelectorAdapter(importer.getIdentities());
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(),
LinearLayoutManager.VERTICAL,
false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new SimpleListDividerDecorator( importer = new WifImporter(bmc, wifData);
ContextCompat.getDrawable(getActivity(), R.drawable.list_divider_h), true)); adapter = new AddressSelectorAdapter(importer.getIdentities());
} catch (IOException e) { LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(),
return super.onCreateView(inflater, container, savedInstanceState); LinearLayoutManager.VERTICAL,
} false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new SimpleListDividerDecorator(
ContextCompat.getDrawable(getActivity(), R.drawable.list_divider_h), true));
view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {

View File

@ -499,14 +499,13 @@ public class MainActivity extends AppCompatActivity
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item); arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item);
Fragment fragment; Fragment fragment;
if (item instanceof Plaintext) if (item instanceof Plaintext) {
fragment = new MessageDetailFragment(); fragment = new MessageDetailFragment();
else if (item instanceof BitmessageAddress) } else if (item instanceof String) {
fragment = new AddressDetailFragment(); fragment = new AddressDetailFragment();
else } else {
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + item.getClass().getSimpleName());
"was " }
+ item.getClass().getSimpleName());
fragment.setArguments(arguments); fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment) .replace(R.id.message_detail_container, fragment)
@ -518,7 +517,7 @@ public class MainActivity extends AppCompatActivity
if (item instanceof Plaintext) { if (item instanceof Plaintext) {
detailIntent = new Intent(this, MessageDetailActivity.class); detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel); detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel);
} else if (item instanceof BitmessageAddress) { } else if (item instanceof String) {
detailIntent = new Intent(this, AddressDetailActivity.class); detailIntent = new Intent(this, AddressDetailActivity.class);
} else { } else {
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +

View File

@ -19,26 +19,27 @@ package ch.dissem.apps.abit.repository;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode;
/** /**
* {@link AddressRepository} implementation using the Android SQL API. * {@link AddressRepository} implementation using the Android SQL API.
*/ */
@ -84,21 +85,25 @@ public class AndroidAddressRepository implements AddressRepository {
return null; return null;
} }
@NonNull
@Override @Override
public List<BitmessageAddress> getIdentities() { public List<BitmessageAddress> getIdentities() {
return find("private_key IS NOT NULL"); return find("private_key IS NOT NULL");
} }
@NonNull
@Override @Override
public List<BitmessageAddress> getChans() { public List<BitmessageAddress> getChans() {
return find("chan = '1'"); return find("chan = '1'");
} }
@NonNull
@Override @Override
public List<BitmessageAddress> getSubscriptions() { public List<BitmessageAddress> getSubscriptions() {
return find("subscribed = '1'"); return find("subscribed = '1'");
} }
@NonNull
@Override @Override
public List<BitmessageAddress> getSubscriptions(long broadcastVersion) { public List<BitmessageAddress> getSubscriptions(long broadcastVersion) {
if (broadcastVersion > 4) { if (broadcastVersion > 4) {
@ -108,11 +113,55 @@ public class AndroidAddressRepository implements AddressRepository {
} }
} }
@NonNull
@Override @Override
public List<BitmessageAddress> getContacts() { public List<BitmessageAddress> getContacts() {
return find("private_key IS NULL OR chan = '1'"); return find("private_key IS NULL OR chan = '1'");
} }
/**
* Returns the contacts in the following order:
* <ul>
* <li>Subscribed addresses come first
* <li>Addresses with Aliases (alphabetically)
* <li>Addresses (alphabetically)
* </ul>
*
* @return the ordered list of ids (address strings)
*/
@NonNull
public List<String> getContactIds() {
return findIds(
"private_key IS NULL OR chan = '1'",
COLUMN_SUBSCRIBED + " DESC, " + COLUMN_ALIAS + " IS NULL, " + COLUMN_ALIAS + ", " + COLUMN_ADDRESS
);
}
@NonNull
private List<String> findIds(String where, String orderBy) {
List<String> result = new LinkedList<>();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_ADDRESS
};
SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null,
orderBy
)) {
while (c.moveToNext()) {
result.add(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
}
}
return result;
}
@NonNull
private List<BitmessageAddress> find(String where) { private List<BitmessageAddress> find(String where) {
List<BitmessageAddress> result = new LinkedList<>(); List<BitmessageAddress> result = new LinkedList<>();
@ -161,8 +210,6 @@ public class AndroidAddressRepository implements AddressRepository {
result.add(address); result.add(address);
} }
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
return result; return result;
} }
@ -188,61 +235,55 @@ public class AndroidAddressRepository implements AddressRepository {
} }
private void update(BitmessageAddress address) { private void update(BitmessageAddress address) {
try { SQLiteDatabase db = sql.getWritableDatabase();
SQLiteDatabase db = sql.getWritableDatabase(); // Create a new map of values, where column names are the keys
// Create a new map of values, where column names are the keys ContentValues values = new ContentValues();
ContentValues values = new ContentValues(); if (address.getAlias() != null) {
if (address.getAlias() != null) { values.put(COLUMN_ALIAS, address.getAlias());
values.put(COLUMN_ALIAS, address.getAlias()); }
} if (address.getPubkey() != null) {
if (address.getPubkey() != null) { ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream(); address.getPubkey().writeUnencrypted(out);
address.getPubkey().writeUnencrypted(out); values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); }
} if (address.getPrivateKey() != null) {
if (address.getPrivateKey() != null) { values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); }
} if (address.isChan()) {
if (address.isChan()) { values.put(COLUMN_CHAN, true);
values.put(COLUMN_CHAN, true); }
} values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address=?", int update = db.update(TABLE_NAME, values, "address=?",
new String[]{address.getAddress()}); new String[]{address.getAddress()});
if (update < 0) { if (update < 0) {
LOG.error("Could not update address " + address); LOG.error("Could not update address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
} }
private void insert(BitmessageAddress address) { private void insert(BitmessageAddress address) {
try { SQLiteDatabase db = sql.getWritableDatabase();
SQLiteDatabase db = sql.getWritableDatabase(); // Create a new map of values, where column names are the keys
// Create a new map of values, where column names are the keys ContentValues values = new ContentValues();
ContentValues values = new ContentValues(); values.put(COLUMN_ADDRESS, address.getAddress());
values.put(COLUMN_ADDRESS, address.getAddress()); values.put(COLUMN_VERSION, address.getVersion());
values.put(COLUMN_VERSION, address.getVersion()); values.put(COLUMN_ALIAS, address.getAlias());
values.put(COLUMN_ALIAS, address.getAlias()); if (address.getPubkey() != null) {
if (address.getPubkey() != null) { ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream(); address.getPubkey().writeUnencrypted(out);
address.getPubkey().writeUnencrypted(out); values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); } else {
} else { values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
values.put(COLUMN_PUBLIC_KEY, (byte[]) null); }
} if (address.getPrivateKey() != null) {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_CHAN, address.isChan()); }
values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); values.put(COLUMN_CHAN, address.isChan());
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
long insert = db.insert(TABLE_NAME, null, values); long insert = db.insert(TABLE_NAME, null, values);
if (insert < 0) { if (insert < 0) {
LOG.error("Could not insert address " + address); LOG.error("Could not insert address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
} }
@ -252,10 +293,21 @@ public class AndroidAddressRepository implements AddressRepository {
db.delete(TABLE_NAME, "address = ?", new String[]{address.getAddress()}); db.delete(TABLE_NAME, "address = ?", new String[]{address.getAddress()});
} }
@NonNull
public BitmessageAddress getById(String id) {
List<BitmessageAddress> result = find("address = '" + id + "'");
if (result.size() > 0) {
return result.get(0);
} else {
throw new ApplicationException("Address with id " + id + " not found.");
}
}
@NonNull
@Override @Override
public BitmessageAddress getAddress(String address) { public BitmessageAddress getAddress(String address) {
List<BitmessageAddress> result = find("address = '" + address + "'"); List<BitmessageAddress> result = find("address = '" + address + "'");
if (result.size() > 0) return result.get(0); if (result.size() > 0) return result.get(0);
return null; return new BitmessageAddress(address);
} }
} }

View File

@ -22,12 +22,12 @@ import android.database.Cursor;
import android.database.DatabaseUtils; import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -91,6 +91,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
this.context = ctx; this.context = ctx;
} }
@NonNull
@Override @Override
public List<Plaintext> findMessages(Label label) { public List<Plaintext> findMessages(Label label) {
if (label == LABEL_ARCHIVE) { if (label == LABEL_ARCHIVE) {
@ -100,6 +101,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
} }
@NonNull
public List<Label> findLabels(String where) { public List<Label> findLabels(String where) {
List<Label> result = new LinkedList<>(); List<Label> result = new LinkedList<>();
@ -156,7 +158,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} else { } else {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND "; where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND ";
args = new String[]{ args = new String[]{
label.getId().toString(), String.valueOf(label.getId()),
Label.Type.UNREAD.name() Label.Type.UNREAD.name()
}; };
} }
@ -168,6 +170,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
); );
} }
@NonNull
@Override @Override
public List<UUID> findConversations(Label label) { public List<UUID> findConversations(Label label) {
String[] projection = { String[] projection = {
@ -202,20 +205,22 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
return; return;
} }
byte[] childIV = message.getInventoryVector().getHash(); byte[] childIV = message.getInventoryVector().getHash();
db.delete(PARENTS_TABLE_NAME, "child=?", new String[]{hex(childIV).toString()}); db.delete(PARENTS_TABLE_NAME, "child=?", new String[]{hex(childIV)});
// save new parents // save new parents
int order = 0; int order = 0;
for (InventoryVector parentIV : message.getParents()) { for (InventoryVector parentIV : message.getParents()) {
Plaintext parent = getMessage(parentIV); Plaintext parent = getMessage(parentIV);
mergeConversations(db, parent.getConversationId(), message.getConversationId()); if (parent != null) {
order++; mergeConversations(db, parent.getConversationId(), message.getConversationId());
ContentValues values = new ContentValues(); order++;
values.put("parent", parentIV.getHash()); ContentValues values = new ContentValues();
values.put("child", childIV); values.put("parent", parentIV.getHash());
values.put("pos", order); values.put("child", childIV);
values.put("conversation", UuidUtils.asBytes(message.getConversationId())); values.put("pos", order);
db.insertOrThrow(PARENTS_TABLE_NAME, null, values); values.put("conversation", UuidUtils.asBytes(message.getConversationId()));
db.insertOrThrow(PARENTS_TABLE_NAME, null, values);
}
} }
} }
@ -229,11 +234,12 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
private void mergeConversations(SQLiteDatabase db, UUID source, UUID target) { private void mergeConversations(SQLiteDatabase db, UUID source, UUID target) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("conversation", UuidUtils.asBytes(target)); values.put("conversation", UuidUtils.asBytes(target));
String[] whereArgs = {hex(UuidUtils.asBytes(source)).toString()}; String[] whereArgs = {hex(UuidUtils.asBytes(source))};
db.update(TABLE_NAME, values, "conversation=?", whereArgs); db.update(TABLE_NAME, values, "conversation=?", whereArgs);
db.update(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs); db.update(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs);
} }
@NonNull
protected List<Plaintext> find(String where) { protected List<Plaintext> find(String where) {
List<Plaintext> result = new LinkedList<>(); List<Plaintext> result = new LinkedList<>();
@ -292,8 +298,6 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
builder.labels(findLabels(id)); builder.labels(findLabels(id));
result.add(builder.build()); result.add(builder.build());
} }
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
return result; return result;
} }
@ -348,7 +352,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
values.put(COLUMN_ACK_DATA, message.getAckData()); values.put(COLUMN_ACK_DATA, message.getAckData());
values.put(COLUMN_SENT, message.getSent()); values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); values.put(COLUMN_STATUS, message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash()); values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
values.put(COLUMN_TTL, message.getTTL()); values.put(COLUMN_TTL, message.getTTL());
values.put(COLUMN_RETRIES, message.getRetries()); values.put(COLUMN_RETRIES, message.getRetries());

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -53,7 +54,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
private void cleanUp() { private void cleanUp() {
SQLiteDatabase db = sql.getWritableDatabase(); SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now(-28 * DAY))}); db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now() - 28 * DAY)});
} }
@Override @Override
@ -82,6 +83,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
} }
} }
@NonNull
@Override @Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
String[] projection = { String[] projection = {
@ -97,7 +99,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
"stream IN (?)", "stream IN (?)",
new String[]{SqlStrings.join(streams).toString()}, new String[]{SqlStrings.join(streams)},
null, null, null, null,
"time DESC", "time DESC",
valueOf(limit) valueOf(limit)
@ -140,7 +142,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
try { try {
cleanUp(); cleanUp();
for (NetworkAddress node : nodes) { for (NetworkAddress node : nodes) {
if (node.getTime() < now(+5 * MINUTE) && node.getTime() > now(-28 * DAY)) { if (node.getTime() < now() + 5 * MINUTE && node.getTime() > now() - 28 * DAY) {
synchronized (this) { synchronized (this) {
Long existing = loadExistingTime(node); Long existing = loadExistingTime(node);
if (existing == null) { if (existing == null) {

View File

@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -65,6 +66,7 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
this.bmc = internalContext; this.bmc = internalContext;
} }
@NonNull
@Override @Override
public Item getItem(byte[] initialHash) { public Item getItem(byte[] initialHash) {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
@ -111,6 +113,7 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
hex(initialHash)); hex(initialHash));
} }
@NonNull
@Override @Override
public List<byte[]> getItems() { public List<byte[]> getItems() {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
@ -139,14 +142,14 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
SQLiteDatabase db = sql.getWritableDatabase(); SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.object)); values.put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.getObjectMessage()));
values.put(COLUMN_DATA, Encode.bytes(item.object)); values.put(COLUMN_DATA, Encode.bytes(item.getObjectMessage()));
values.put(COLUMN_VERSION, item.object.getVersion()); values.put(COLUMN_VERSION, item.getObjectMessage().getVersion());
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, item.nonceTrialsPerByte); values.put(COLUMN_NONCE_TRIALS_PER_BYTE, item.getNonceTrialsPerByte());
values.put(COLUMN_EXTRA_BYTES, item.extraBytes); values.put(COLUMN_EXTRA_BYTES, item.getExtraBytes());
if (item.message != null) { if (item.getMessage() != null) {
values.put(COLUMN_EXPIRATION_TIME, item.expirationTime); values.put(COLUMN_EXPIRATION_TIME, item.getExpirationTime());
values.put(COLUMN_MESSAGE_ID, (Long) item.message.getId()); values.put(COLUMN_MESSAGE_ID, (Long) item.getMessage().getId());
} }
db.insertOrThrow(TABLE_NAME, null, values); db.insertOrThrow(TABLE_NAME, null, values);

View File

@ -100,7 +100,7 @@ public class BitmessageService extends Service {
if (bmc != null) { if (bmc != null) {
return bmc.status(); return bmc.status();
} else { } else {
return new Property("bitmessage context", null); return new Property("bitmessage context");
} }
} }
} }

View File

@ -99,12 +99,12 @@ public class Singleton {
return messageListener; return messageListener;
} }
public static MessageRepository getMessageRepository(Context ctx) { public static AndroidMessageRepository getMessageRepository(Context ctx) {
return getBitmessageContext(ctx).messages(); return (AndroidMessageRepository) getBitmessageContext(ctx).messages();
} }
public static AddressRepository getAddressRepository(Context ctx) { public static AndroidAddressRepository getAddressRepository(Context ctx) {
return getBitmessageContext(ctx).addresses(); return (AndroidAddressRepository) getBitmessageContext(ctx).addresses();
} }
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) { public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {

View File

@ -109,6 +109,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
LOG.info("Looking for completed POW"); LOG.info("Looking for completed POW");
BitmessageAddress identity = Singleton.getIdentity(getContext()); BitmessageAddress identity = Singleton.getIdentity(getContext());
@SuppressWarnings("ConstantConditions")
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey(); byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
byte[] signingKey = cryptography().createPublicKey(identity.getPublicDecryptionKey()); byte[] signingKey = cryptography().createPublicKey(identity.getPublicDecryptionKey());
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity); ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
@ -116,8 +117,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
List<byte[]> items = powRepo.getItems(); List<byte[]> items = powRepo.getItems();
for (byte[] initialHash : items) { for (byte[] initialHash : items) {
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash); ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
byte[] target = cryptography().getProofOfWorkTarget(item.object, item byte[] target = cryptography().getProofOfWorkTarget(item.getObjectMessage(), item.getNonceTrialsPerByte(), item.getExtraBytes());
.nonceTrialsPerByte, item.extraBytes);
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>( CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target)); new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
cryptoMsg.signAndEncrypt(identity, signingKey); cryptoMsg.signAndEncrypt(identity, signingKey);

View File

@ -34,12 +34,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import ch.dissem.apps.abit.Identicon; import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.exception.ApplicationException;
import static android.graphics.Color.BLACK; import static android.graphics.Color.BLACK;
import static android.graphics.Color.WHITE; import static android.graphics.Color.WHITE;
@ -83,11 +81,7 @@ public class Drawables {
if (address.getPubkey() != null) { if (address.getPubkey() != null) {
link.append(address.getAlias() == null ? '?' : '&'); link.append(address.getAlias() == null ? '?' : '&');
ByteArrayOutputStream pubkey = new ByteArrayOutputStream(); ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
try { address.getPubkey().writeUnencrypted(pubkey);
address.getPubkey().writeUnencrypted(pubkey);
} catch (IOException e) {
throw new ApplicationException(e);
}
link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE | NO_WRAP)); link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE | NO_WRAP));
} }
BitMatrix result; BitMatrix result;

View File

@ -15,9 +15,13 @@ import java.util.UUID;
* </p> * </p>
*/ */
public class UuidUtils { public class UuidUtils {
/**
* @param bytes that represent a UUID, or null for a random UUID
* @return the UUID from the given bytes, or a random UUID if bytes is null.
*/
public static UUID asUuid(byte[] bytes) { public static UUID asUuid(byte[] bytes) {
if (bytes == null) { if (bytes == null) {
return null; return UUID.randomUUID();
} }
ByteBuffer bb = ByteBuffer.wrap(bytes); ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong(); long firstLong = bb.getLong();

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/colorAccent"/>
<size
android:width="40dp"
android:height="40dp"/>
</shape>

View File

@ -29,7 +29,7 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_margin="16dp" android:layout_margin="16dp"
android:src="@color/colorAccent" android:src="@drawable/avatar_placeholder"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription"/>
<TextView <TextView

View File

@ -65,7 +65,7 @@
<string name="pubkey_available">Public key available</string> <string name="pubkey_available">Public key available</string>
<string name="pubkey_not_available">Public key not yet available</string> <string name="pubkey_not_available">Public key not yet available</string>
<string name="alt_qr_code">QR code</string> <string name="alt_qr_code">QR code</string>
<string name="add_identity_warning">Having more identities will reequire more resources. If you are sure you want to add an identity, please select what exactly you want to do:</string> <string name="add_identity_warning">Having more identities will require more resources. If you are sure you want to add an identity, please select what exactly you want to do:</string>
<string name="share">Share</string> <string name="share">Share</string>
<string name="delete_identity_warning">Are you sure you want to delete this identity? You won\'t be able to receive any messages sent to this address and can\'t undo this operation.</string> <string name="delete_identity_warning">Are you sure you want to delete this identity? You won\'t be able to receive any messages sent to this address and can\'t undo this operation.</string>
<string name="delete_contact_warning">Are you sure you want to delete this contact?</string> <string name="delete_contact_warning">Are you sure you want to delete this contact?</string>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.14.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.14.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong