Asynchronously load contacts to improve responsiveness
This commit is contained in:
		| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|                         } | ||||
|                     } else if (rhs.getAlias() != null) { | ||||
|                         return 1; | ||||
|                     } else { | ||||
|                         return lhs.getAddress().compareTo(rhs.getAddress()); | ||||
|                     } | ||||
|             protected Void doInBackground(Void... params) { | ||||
|                 List<String> ids = addressRepo.getContactIds(); | ||||
|                 for (String id : ids) { | ||||
|                     BitmessageAddress address = addressRepo.getById(id); | ||||
|                     publishProgress(address); | ||||
|                 } | ||||
|                 if (lhs.isSubscribed()) { | ||||
|                     return -1; | ||||
|                 } else { | ||||
|                     return 1; | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected void onProgressUpdate(BitmessageAddress... values) { | ||||
|                 for (BitmessageAddress address : values) { | ||||
|                     adapter.add(address); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         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; | ||||
|             } | ||||
|         }); | ||||
|         }.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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -52,21 +52,19 @@ 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(), | ||||
|                 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)); | ||||
|         } catch (IOException e) { | ||||
|             return super.onCreateView(inflater, container, savedInstanceState); | ||||
|         } | ||||
|         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( | ||||
|             ContextCompat.getDrawable(getActivity(), R.drawable.list_divider_h), true)); | ||||
|  | ||||
|         view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() { | ||||
|             @Override | ||||
|             public void onClick(View view) { | ||||
|   | ||||
| @@ -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 " + | ||||
|   | ||||
| @@ -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,61 +235,55 @@ 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(); | ||||
|             if (address.getAlias() != null) { | ||||
|                 values.put(COLUMN_ALIAS, address.getAlias()); | ||||
|             } | ||||
|             if (address.getPubkey() != null) { | ||||
|                 ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||
|                 address.getPubkey().writeUnencrypted(out); | ||||
|                 values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); | ||||
|             } | ||||
|             if (address.getPrivateKey() != null) { | ||||
|                 values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); | ||||
|             } | ||||
|             if (address.isChan()) { | ||||
|                 values.put(COLUMN_CHAN, true); | ||||
|             } | ||||
|             values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); | ||||
|         SQLiteDatabase db = sql.getWritableDatabase(); | ||||
|         // Create a new map of values, where column names are the keys | ||||
|         ContentValues values = new ContentValues(); | ||||
|         if (address.getAlias() != null) { | ||||
|             values.put(COLUMN_ALIAS, address.getAlias()); | ||||
|         } | ||||
|         if (address.getPubkey() != null) { | ||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||
|             address.getPubkey().writeUnencrypted(out); | ||||
|             values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); | ||||
|         } | ||||
|         if (address.getPrivateKey() != null) { | ||||
|             values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); | ||||
|         } | ||||
|         if (address.isChan()) { | ||||
|             values.put(COLUMN_CHAN, true); | ||||
|         } | ||||
|         values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); | ||||
|  | ||||
|             int update = db.update(TABLE_NAME, values, "address=?", | ||||
|                 new String[]{address.getAddress()}); | ||||
|             if (update < 0) { | ||||
|                 LOG.error("Could not update address " + address); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             LOG.error(e.getMessage(), e); | ||||
|         int update = db.update(TABLE_NAME, values, "address=?", | ||||
|             new String[]{address.getAddress()}); | ||||
|         if (update < 0) { | ||||
|             LOG.error("Could not update address " + address); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(); | ||||
|             values.put(COLUMN_ADDRESS, address.getAddress()); | ||||
|             values.put(COLUMN_VERSION, address.getVersion()); | ||||
|             values.put(COLUMN_ALIAS, address.getAlias()); | ||||
|             if (address.getPubkey() != null) { | ||||
|                 ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||
|                 address.getPubkey().writeUnencrypted(out); | ||||
|                 values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); | ||||
|             } else { | ||||
|                 values.put(COLUMN_PUBLIC_KEY, (byte[]) null); | ||||
|             } | ||||
|         SQLiteDatabase db = sql.getWritableDatabase(); | ||||
|         // Create a new map of values, where column names are the keys | ||||
|         ContentValues values = new ContentValues(); | ||||
|         values.put(COLUMN_ADDRESS, address.getAddress()); | ||||
|         values.put(COLUMN_VERSION, address.getVersion()); | ||||
|         values.put(COLUMN_ALIAS, address.getAlias()); | ||||
|         if (address.getPubkey() != null) { | ||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||||
|             address.getPubkey().writeUnencrypted(out); | ||||
|             values.put(COLUMN_PUBLIC_KEY, out.toByteArray()); | ||||
|         } 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()); | ||||
|         } | ||||
|         values.put(COLUMN_CHAN, address.isChan()); | ||||
|         values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); | ||||
|  | ||||
|             long insert = db.insert(TABLE_NAME, null, values); | ||||
|             if (insert < 0) { | ||||
|                 LOG.error("Could not insert address " + address); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             LOG.error(e.getMessage(), e); | ||||
|         long insert = db.insert(TABLE_NAME, null, values); | ||||
|         if (insert < 0) { | ||||
|             LOG.error("Could not insert address " + address); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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,20 +205,22 @@ 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); | ||||
|             mergeConversations(db, parent.getConversationId(), message.getConversationId()); | ||||
|             order++; | ||||
|             ContentValues values = new ContentValues(); | ||||
|             values.put("parent", parentIV.getHash()); | ||||
|             values.put("child", childIV); | ||||
|             values.put("pos", order); | ||||
|             values.put("conversation", UuidUtils.asBytes(message.getConversationId())); | ||||
|             db.insertOrThrow(PARENTS_TABLE_NAME, null, values); | ||||
|             if (parent != null) { | ||||
|                 mergeConversations(db, parent.getConversationId(), message.getConversationId()); | ||||
|                 order++; | ||||
|                 ContentValues values = new ContentValues(); | ||||
|                 values.put("parent", parentIV.getHash()); | ||||
|                 values.put("child", childIV); | ||||
|                 values.put("pos", order); | ||||
|                 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) { | ||||
|         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()); | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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); | ||||
|             } | ||||
|             address.getPubkey().writeUnencrypted(pubkey); | ||||
|             link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE | NO_WRAP)); | ||||
|         } | ||||
|         BitMatrix result; | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
							
								
								
									
										12
									
								
								app/src/main/res/drawable/avatar_placeholder.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/src/main/res/drawable/avatar_placeholder.xml
									
									
									
									
									
										Normal 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> | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user