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);
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);
}
setHasOptionsMenu(true);

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -32,11 +33,11 @@ import android.widget.TextView;
import com.google.zxing.integration.android.IntentIntegrator;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
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.
*/
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
public void onResume() {
super.onResume();
@ -55,61 +96,26 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
}
public void updateList() {
List<BitmessageAddress> addresses = Singleton.getAddressRepository(getContext())
.getContacts();
Collections.sort(addresses, new Comparator<BitmessageAddress>() {
adapter.clear();
final AndroidAddressRepository addressRepo = Singleton.getAddressRepository(getContext());
new AsyncTask<Void, BitmessageAddress, Void>() {
@Override
public int compare(BitmessageAddress lhs, BitmessageAddress rhs) {
// Yields the following order:
// * Subscribed addresses come first
// * Addresses with Aliases (alphabetically)
// * Addresses (alphabetically)
if (lhs.isSubscribed() == rhs.isSubscribed()) {
if (lhs.getAlias() != null) {
if (rhs.getAlias() != null) {
return lhs.getAlias().compareTo(rhs.getAlias());
} else {
return -1;
protected Void doInBackground(Void... params) {
List<String> ids = addressRepo.getContactIds();
for (String id : ids) {
BitmessageAddress address = addressRepo.getById(id);
publishProgress(address);
}
} else if (rhs.getAlias() != null) {
return 1;
} else {
return lhs.getAddress().compareTo(rhs.getAddress());
return null;
}
}
if (lhs.isSubscribed()) {
return -1;
} else {
return 1;
}
}
});
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);
protected void onProgressUpdate(BitmessageAddress... values) {
for (BitmessageAddress address : values) {
adapter.add(address);
}
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;
}
});
}.execute();
}
@Override
@ -163,4 +169,12 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
public void updateList(Label label) {
updateList();
}
private static class ViewHolder {
private Context ctx;
private ImageView avatar;
private TextView name;
private TextView streamNumber;
private View subscribed;
}
}

View File

@ -52,7 +52,7 @@ public class ImportIdentitiesFragment extends Fragment {
String wifData = getArguments().getString(WIF_DATA);
BitmessageContext bmc = Singleton.getBitmessageContext(getActivity());
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(),
@ -64,9 +64,7 @@ public class ImportIdentitiesFragment extends Fragment {
recyclerView.addItemDecoration(new SimpleListDividerDecorator(
ContextCompat.getDrawable(getActivity(), R.drawable.list_divider_h), true));
} catch (IOException e) {
return super.onCreateView(inflater, container, savedInstanceState);
}
view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

View File

@ -499,14 +499,13 @@ public class MainActivity extends AppCompatActivity
Bundle arguments = new Bundle();
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item);
Fragment fragment;
if (item instanceof Plaintext)
if (item instanceof Plaintext) {
fragment = new MessageDetailFragment();
else if (item instanceof BitmessageAddress)
} else if (item instanceof String) {
fragment = new AddressDetailFragment();
else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
} else {
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + item.getClass().getSimpleName());
}
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment)
@ -518,7 +517,7 @@ public class MainActivity extends AppCompatActivity
if (item instanceof Plaintext) {
detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel);
} else if (item instanceof BitmessageAddress) {
} else if (item instanceof String) {
detailIntent = new Intent(this, AddressDetailActivity.class);
} else {
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.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
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 android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
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.
*/
@ -84,21 +85,25 @@ public class AndroidAddressRepository implements AddressRepository {
return null;
}
@NonNull
@Override
public List<BitmessageAddress> getIdentities() {
return find("private_key IS NOT NULL");
}
@NonNull
@Override
public List<BitmessageAddress> getChans() {
return find("chan = '1'");
}
@NonNull
@Override
public List<BitmessageAddress> getSubscriptions() {
return find("subscribed = '1'");
}
@NonNull
@Override
public List<BitmessageAddress> getSubscriptions(long broadcastVersion) {
if (broadcastVersion > 4) {
@ -108,11 +113,55 @@ public class AndroidAddressRepository implements AddressRepository {
}
}
@NonNull
@Override
public List<BitmessageAddress> getContacts() {
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) {
List<BitmessageAddress> result = new LinkedList<>();
@ -161,8 +210,6 @@ public class AndroidAddressRepository implements AddressRepository {
result.add(address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return result;
}
@ -188,7 +235,6 @@ public class AndroidAddressRepository implements AddressRepository {
}
private void update(BitmessageAddress address) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
@ -213,13 +259,9 @@ public class AndroidAddressRepository implements AddressRepository {
if (update < 0) {
LOG.error("Could not update address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
private void insert(BitmessageAddress address) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
@ -233,7 +275,9 @@ public class AndroidAddressRepository implements AddressRepository {
} else {
values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
}
if (address.getPrivateKey() != null) {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
}
values.put(COLUMN_CHAN, address.isChan());
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
@ -241,9 +285,6 @@ public class AndroidAddressRepository implements AddressRepository {
if (insert < 0) {
LOG.error("Could not insert address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
@ -252,10 +293,21 @@ public class AndroidAddressRepository implements AddressRepository {
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
public BitmessageAddress getAddress(String address) {
List<BitmessageAddress> result = find("address = '" + address + "'");
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.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@ -91,6 +91,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
this.context = ctx;
}
@NonNull
@Override
public List<Plaintext> findMessages(Label label) {
if (label == LABEL_ARCHIVE) {
@ -100,6 +101,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
}
}
@NonNull
public List<Label> findLabels(String where) {
List<Label> result = new LinkedList<>();
@ -156,7 +158,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} else {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND ";
args = new String[]{
label.getId().toString(),
String.valueOf(label.getId()),
Label.Type.UNREAD.name()
};
}
@ -168,6 +170,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
);
}
@NonNull
@Override
public List<UUID> findConversations(Label label) {
String[] projection = {
@ -202,12 +205,13 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
return;
}
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
int order = 0;
for (InventoryVector parentIV : message.getParents()) {
Plaintext parent = getMessage(parentIV);
if (parent != null) {
mergeConversations(db, parent.getConversationId(), message.getConversationId());
order++;
ContentValues values = new ContentValues();
@ -218,6 +222,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
db.insertOrThrow(PARENTS_TABLE_NAME, null, values);
}
}
}
/**
* Replaces every occurrence of the source conversation ID with the target ID
@ -229,11 +234,12 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
private void mergeConversations(SQLiteDatabase db, UUID source, UUID target) {
ContentValues values = new ContentValues();
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(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs);
}
@NonNull
protected List<Plaintext> find(String where) {
List<Plaintext> result = new LinkedList<>();
@ -292,8 +298,6 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
builder.labels(findLabels(id));
result.add(builder.build());
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return result;
}
@ -348,7 +352,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
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_STATUS, message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
values.put(COLUMN_TTL, message.getTTL());
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.SQLiteDoneException;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,7 +54,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
private void cleanUp() {
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
@ -82,6 +83,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
}
}
@NonNull
@Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
String[] projection = {
@ -97,7 +99,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
try (Cursor c = db.query(
TABLE_NAME, projection,
"stream IN (?)",
new String[]{SqlStrings.join(streams).toString()},
new String[]{SqlStrings.join(streams)},
null, null,
"time DESC",
valueOf(limit)
@ -140,7 +142,7 @@ public class AndroidNodeRegistry implements NodeRegistry {
try {
cleanUp();
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) {
Long existing = loadExistingTime(node);
if (existing == null) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,9 +15,13 @@ import java.util.UUID;
* </p>
*/
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) {
if (bytes == null) {
return null;
return UUID.randomUUID();
}
ByteBuffer bb = ByteBuffer.wrap(bytes);
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_alignParentTop="true"
android:layout_margin="16dp"
android:src="@color/colorAccent"
android:src="@drawable/avatar_placeholder"
tools:ignore="ContentDescription"/>
<TextView

View File

@ -65,7 +65,7 @@
<string name="pubkey_available">Public key available</string>
<string name="pubkey_not_available">Public key not yet available</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="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>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter()
}
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'
// NOTE: Do not place your application dependencies here; they belong