Merge branch 'feature/server-pow' into develop
This commit is contained in:
		| @@ -29,6 +29,7 @@ dependencies { | |||||||
|     compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT' |     compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT' | ||||||
|     compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT' |     compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT' | ||||||
|     compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT' |     compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT' | ||||||
|  |     compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT' | ||||||
|  |  | ||||||
|     compile 'org.slf4j:slf4j-android:1.7.12' |     compile 'org.slf4j:slf4j-android:1.7.12' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
|         android:label="@string/app_name" |         android:label="@string/app_name" | ||||||
|         android:theme="@style/AppTheme"> |         android:theme="@style/AppTheme"> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".MessageListActivity" |             android:name=".MainActivity" | ||||||
|             android:label="@string/app_name"> |             android:label="@string/app_name"> | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.MAIN" /> |                 <action android:name="android.intent.action.MAIN" /> | ||||||
| @@ -29,28 +29,28 @@ | |||||||
|         <activity |         <activity | ||||||
|             android:name=".MessageDetailActivity" |             android:name=".MessageDetailActivity" | ||||||
|             android:label="@string/title_message_detail" |             android:label="@string/title_message_detail" | ||||||
|             android:parentActivityName=".MessageListActivity" |             android:parentActivityName=".MainActivity" | ||||||
|             tools:ignore="UnusedAttribute"> |             tools:ignore="UnusedAttribute"> | ||||||
|             <meta-data |             <meta-data | ||||||
|                 android:name="android.support.PARENT_ACTIVITY" |                 android:name="android.support.PARENT_ACTIVITY" | ||||||
|                 android:value=".MessageListActivity" /> |                 android:value=".MainActivity" /> | ||||||
|         </activity> |         </activity> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".SubscriptionDetailActivity" |             android:name=".SubscriptionDetailActivity" | ||||||
|             android:label="@string/title_subscription_detail" |             android:label="@string/title_subscription_detail" | ||||||
|             android:parentActivityName=".MessageListActivity" |             android:parentActivityName=".MainActivity" | ||||||
|             tools:ignore="UnusedAttribute"> |             tools:ignore="UnusedAttribute"> | ||||||
|             <meta-data |             <meta-data | ||||||
|                 android:name="android.support.PARENT_ACTIVITY" |                 android:name="android.support.PARENT_ACTIVITY" | ||||||
|                 android:value=".MessageListActivity" /> |                 android:value=".MainActivity" /> | ||||||
|         </activity> |         </activity> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".ComposeMessageActivity" |             android:name=".ComposeMessageActivity" | ||||||
|             android:label="Compose" |             android:label="Compose" | ||||||
|             android:parentActivityName=".MessageListActivity"> |             android:parentActivityName=".MainActivity"> | ||||||
|             <meta-data |             <meta-data | ||||||
|                 android:name="android.support.PARENT_ACTIVITY" |                 android:name="android.support.PARENT_ACTIVITY" | ||||||
|                 android:value=".MessageListActivity" /> |                 android:value=".MainActivity" /> | ||||||
|  |  | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.SENDTO" /> |                 <action android:name="android.intent.action.SENDTO" /> | ||||||
| @@ -79,7 +79,7 @@ | |||||||
|         <activity |         <activity | ||||||
|             android:name=".SettingsActivity" |             android:name=".SettingsActivity" | ||||||
|             android:label="@string/settings" |             android:label="@string/settings" | ||||||
|             android:parentActivityName=".MessageListActivity"> |             android:parentActivityName=".MainActivity"> | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> |                 <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ CREATE TABLE Message ( | |||||||
|   sent                    INTEGER, |   sent                    INTEGER, | ||||||
|   received                INTEGER, |   received                INTEGER, | ||||||
|   status                  VARCHAR(20)   NOT NULL, |   status                  VARCHAR(20)   NOT NULL, | ||||||
|  |   initial_hash            BINARY(64)    UNIQUE, | ||||||
|  |  | ||||||
|   FOREIGN KEY (sender)    REFERENCES Address (address), |   FOREIGN KEY (sender)    REFERENCES Address (address), | ||||||
|   FOREIGN KEY (recipient) REFERENCES Address (address) |   FOREIGN KEY (recipient) REFERENCES Address (address) | ||||||
|   | |||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | -- This is done in V1.2, as SQLite doesn't support ADD CONSTRAINT and a proper migration | ||||||
|  | -- wasn't really necessary yet. | ||||||
|  | -- | ||||||
|  | -- This file is here to reduce confusion regarding to the original migration files. | ||||||
|  |  | ||||||
|  | --ALTER TABLE Message ADD COLUMN initial_hash BINARY(64); | ||||||
|  | --ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash); | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | CREATE TABLE POW ( | ||||||
|  |   initial_hash          BINARY(64)    PRIMARY KEY, | ||||||
|  |   data                  BLOB          NOT NULL, | ||||||
|  |   version               BIGINT        NOT NULL, | ||||||
|  |   nonce_trials_per_byte BIGINT        NOT NULL, | ||||||
|  |   extra_bytes           BIGINT        NOT NULL | ||||||
|  | ); | ||||||
| @@ -42,12 +42,16 @@ import org.slf4j.LoggerFactory; | |||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| import java.lang.ref.WeakReference; | import java.lang.ref.WeakReference; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import ch.dissem.apps.abit.listener.ActionBarListener; | import ch.dissem.apps.abit.listener.ActionBarListener; | ||||||
| import ch.dissem.apps.abit.listener.ListSelectionListener; | import ch.dissem.apps.abit.listener.ListSelectionListener; | ||||||
| import ch.dissem.apps.abit.service.BitmessageService; | import ch.dissem.apps.abit.service.BitmessageService; | ||||||
| import ch.dissem.apps.abit.service.Singleton; | import ch.dissem.apps.abit.service.Singleton; | ||||||
| import ch.dissem.apps.abit.synchronization.Authenticator; | import ch.dissem.apps.abit.synchronization.Authenticator; | ||||||
|  | import ch.dissem.apps.abit.synchronization.SyncAdapter; | ||||||
|  | import ch.dissem.apps.abit.util.Preferences; | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; | import ch.dissem.bitmessage.entity.Plaintext; | ||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; | import ch.dissem.bitmessage.entity.valueobject.Label; | ||||||
| @@ -77,13 +81,12 @@ import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY; | |||||||
|  * to listen for item selections. |  * to listen for item selections. | ||||||
|  * </p> |  * </p> | ||||||
|  */ |  */ | ||||||
| public class MessageListActivity extends AppCompatActivity | public class MainActivity extends AppCompatActivity | ||||||
|         implements ListSelectionListener<Serializable>, ActionBarListener { |         implements ListSelectionListener<Serializable>, ActionBarListener { | ||||||
|     public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; |     public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; | ||||||
|     public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; |     public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; | ||||||
| 
 | 
 | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class); |     private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class); | ||||||
|     private static final long SYNC_FREQUENCY = 15 * 60; // seconds |  | ||||||
|     private static final int ADD_IDENTITY = 1; |     private static final int ADD_IDENTITY = 1; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -99,8 +102,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|     private static ServiceConnection connection = new ServiceConnection() { |     private static ServiceConnection connection = new ServiceConnection() { | ||||||
|         @Override |         @Override | ||||||
|         public void onServiceConnected(ComponentName name, IBinder service) { |         public void onServiceConnected(ComponentName name, IBinder service) { | ||||||
|             MessageListActivity.service = new Messenger(service); |             MainActivity.service = new Messenger(service); | ||||||
|             MessageListActivity.bound = true; |             MainActivity.bound = true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
| @@ -121,8 +124,10 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         messageRepo = Singleton.getMessageRepository(this); |         messageRepo = Singleton.getMessageRepository(this); | ||||||
|         addressRepo = Singleton.getAddressRepository(this); |         addressRepo = Singleton.getAddressRepository(this); | ||||||
| 
 |         List<Label> labels = messageRepo.getLabels(); | ||||||
|         selectedLabel = messageRepo.getLabels().get(0); |         if (selectedLabel == null) { | ||||||
|  |             selectedLabel = labels.get(0); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         setContentView(R.layout.activity_message_list); |         setContentView(R.layout.activity_message_list); | ||||||
| 
 | 
 | ||||||
| @@ -130,7 +135,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|         setSupportActionBar(toolbar); |         setSupportActionBar(toolbar); | ||||||
| 
 | 
 | ||||||
|         MessageListFragment listFragment = new MessageListFragment(); |         MessageListFragment listFragment = new MessageListFragment(); | ||||||
|         getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit(); |         getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment) | ||||||
|  |                 .commit(); | ||||||
| 
 | 
 | ||||||
|         if (findViewById(R.id.message_detail_container) != null) { |         if (findViewById(R.id.message_detail_container) != null) { | ||||||
|             // The detail container view will be present only in the |             // The detail container view will be present only in the | ||||||
| @@ -144,7 +150,7 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|             listFragment.setActivateOnItemClick(true); |             listFragment.setActivateOnItemClick(true); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         createDrawer(toolbar); |         createDrawer(toolbar, labels); | ||||||
| 
 | 
 | ||||||
|         Singleton.getMessageListener(this).resetNotification(); |         Singleton.getMessageListener(this).resetNotification(); | ||||||
| 
 | 
 | ||||||
| @@ -153,21 +159,10 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|             onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE)); |             onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         createSyncAccount(); |         if (Preferences.useTrustedNode(this)) { | ||||||
|     } |             SyncAdapter.startSync(this); | ||||||
| 
 |         } else { | ||||||
|     private void createSyncAccount() { |             SyncAdapter.stopSync(this); | ||||||
|         // Create account, if it's missing. (Either first run, or user has deleted account.) |  | ||||||
|         Account account = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE); |  | ||||||
| 
 |  | ||||||
|         if (AccountManager.get(this).addAccountExplicitly(account, null, null)) { |  | ||||||
|             // Inform the system that this account supports sync |  | ||||||
|             ContentResolver.setIsSyncable(account, AUTHORITY, 1); |  | ||||||
|             // Inform the system that this account is eligible for auto sync when the network is up |  | ||||||
|             ContentResolver.setSyncAutomatically(account, AUTHORITY, true); |  | ||||||
|             // Recommend a schedule for automatic synchronization. The system may modify this based |  | ||||||
|             // on other scheduled syncs and network utilization. |  | ||||||
|             ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -185,7 +180,7 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void createDrawer(Toolbar toolbar) { |     private void createDrawer(Toolbar toolbar, Collection<Label> labels) { | ||||||
|         final ArrayList<IProfile> profiles = new ArrayList<>(); |         final ArrayList<IProfile> profiles = new ArrayList<>(); | ||||||
|         for (BitmessageAddress identity : addressRepo.getIdentities()) { |         for (BitmessageAddress identity : addressRepo.getIdentities()) { | ||||||
|             LOG.info("Adding identity " + identity.getAddress()); |             LOG.info("Adding identity " + identity.getAddress()); | ||||||
| @@ -216,10 +211,12 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                 .withProfiles(profiles) |                 .withProfiles(profiles) | ||||||
|                 .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { |                 .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { | ||||||
|                     @Override |                     @Override | ||||||
|                     public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { |                     public boolean onProfileChanged(View view, IProfile profile, boolean | ||||||
|  |                             currentProfile) { | ||||||
|                         if (profile.getIdentifier() == ADD_IDENTITY) { |                         if (profile.getIdentifier() == ADD_IDENTITY) { | ||||||
|                             try { |                             try { | ||||||
|                                 Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY); |                                 Message message = Message.obtain(null, BitmessageService | ||||||
|  |                                         .MSG_CREATE_IDENTITY); | ||||||
|                                 message.replyTo = messenger; |                                 message.replyTo = messenger; | ||||||
|                                 service.send(message); |                                 service.send(message); | ||||||
|                             } catch (RemoteException e) { |                             } catch (RemoteException e) { | ||||||
| @@ -236,14 +233,15 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|                 .build(); |                 .build(); | ||||||
|         if (profiles.size() > 0) { |         if (profiles.size() > 2) { // There's always the add and manage identity items | ||||||
|             accountHeader.setActiveProfile(profiles.get(0), true); |             accountHeader.setActiveProfile(profiles.get(0), true); | ||||||
|         } |         } | ||||||
|         incomingHandler.updateAccountHeader(accountHeader); |         incomingHandler.updateAccountHeader(accountHeader); | ||||||
| 
 | 
 | ||||||
|         ArrayList<IDrawerItem> drawerItems = new ArrayList<>(); |         ArrayList<IDrawerItem> drawerItems = new ArrayList<>(); | ||||||
|         for (Label label : messageRepo.getLabels()) { |         for (Label label : labels) { | ||||||
|             PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label); |             PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag | ||||||
|  |                     (label); | ||||||
|             if (label.getType() == null) { |             if (label.getType() == null) { | ||||||
|                 item.withIcon(CommunityMaterial.Icon.cmd_label); |                 item.withIcon(CommunityMaterial.Icon.cmd_label); | ||||||
|             } else { |             } else { | ||||||
| @@ -297,17 +295,21 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                                 .withChecked(BitmessageService.isRunning()) |                                 .withChecked(BitmessageService.isRunning()) | ||||||
|                                 .withOnCheckedChangeListener(new OnCheckedChangeListener() { |                                 .withOnCheckedChangeListener(new OnCheckedChangeListener() { | ||||||
|                                     @Override |                                     @Override | ||||||
|                                     public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) { |                                     public void onCheckedChanged(IDrawerItem drawerItem, | ||||||
|  |                                                                  CompoundButton buttonView, | ||||||
|  |                                                                  boolean isChecked) { | ||||||
|                                         if (messenger != null) { |                                         if (messenger != null) { | ||||||
|                                             if (isChecked) { |                                             if (isChecked) { | ||||||
|                                                 try { |                                                 try { | ||||||
|                                                     service.send(Message.obtain(null, MSG_START_NODE)); |                                                     service.send(Message.obtain(null, | ||||||
|  |                                                             MSG_START_NODE)); | ||||||
|                                                 } catch (RemoteException e) { |                                                 } catch (RemoteException e) { | ||||||
|                                                     LOG.error(e.getMessage(), e); |                                                     LOG.error(e.getMessage(), e); | ||||||
|                                                 } |                                                 } | ||||||
|                                             } else { |                                             } else { | ||||||
|                                                 try { |                                                 try { | ||||||
|                                                     service.send(Message.obtain(null, MSG_STOP_NODE)); |                                                     service.send(Message.obtain(null, | ||||||
|  |                                                             MSG_STOP_NODE)); | ||||||
|                                                 } catch (RemoteException e) { |                                                 } catch (RemoteException e) { | ||||||
|                                                     LOG.error(e.getMessage(), e); |                                                     LOG.error(e.getMessage(), e); | ||||||
|                                                 } |                                                 } | ||||||
| @@ -318,7 +320,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                 ) |                 ) | ||||||
|                 .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { |                 .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { | ||||||
|                     @Override |                     @Override | ||||||
|                     public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) { |                     public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long | ||||||
|  |                             l, IDrawerItem item) { | ||||||
|                         if (item.getTag() instanceof Label) { |                         if (item.getTag() instanceof Label) { | ||||||
|                             selectedLabel = (Label) item.getTag(); |                             selectedLabel = (Label) item.getTag(); | ||||||
|                             showSelectedLabel(); |                             showSelectedLabel(); | ||||||
| @@ -327,15 +330,18 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                             Nameable<?> ni = (Nameable<?>) item; |                             Nameable<?> ni = (Nameable<?>) item; | ||||||
|                             switch (ni.getNameRes()) { |                             switch (ni.getNameRes()) { | ||||||
|                                 case R.string.contacts_and_subscriptions: |                                 case R.string.contacts_and_subscriptions: | ||||||
|                                     if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) { |                                     if (!(getSupportFragmentManager().findFragmentById(R.id | ||||||
|  |                                             .item_list) instanceof SubscriptionListFragment)) { | ||||||
|                                         changeList(new SubscriptionListFragment()); |                                         changeList(new SubscriptionListFragment()); | ||||||
|                                     } else { |                                     } else { | ||||||
|                                         ((SubscriptionListFragment) getSupportFragmentManager() |                                         ((SubscriptionListFragment) getSupportFragmentManager() | ||||||
|                                                 .findFragmentById(R.id.item_list)).updateList(); |                                                 .findFragmentById(R.id.item_list)).updateList(); | ||||||
|                                     } |                                     } | ||||||
|  | 
 | ||||||
|                                     break; |                                     break; | ||||||
|                                 case R.string.settings: |                                 case R.string.settings: | ||||||
|                                     startActivity(new Intent(MessageListActivity.this, SettingsActivity.class)); |                                     startActivity(new Intent(MainActivity.this, SettingsActivity | ||||||
|  |                                             .class)); | ||||||
|                                     break; |                                     break; | ||||||
|                                 case R.string.archive: |                                 case R.string.archive: | ||||||
|                                     selectedLabel = null; |                                     selectedLabel = null; | ||||||
| @@ -353,7 +359,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void showSelectedLabel() { |     private void showSelectedLabel() { | ||||||
|         if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) { |         if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof | ||||||
|  |                 MessageListFragment) { | ||||||
|             ((MessageListFragment) getSupportFragmentManager() |             ((MessageListFragment) getSupportFragmentManager() | ||||||
|                     .findFragmentById(R.id.item_list)).updateList(selectedLabel); |                     .findFragmentById(R.id.item_list)).updateList(selectedLabel); | ||||||
|         } else { |         } else { | ||||||
| @@ -381,7 +388,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|             else if (item instanceof BitmessageAddress) |             else if (item instanceof BitmessageAddress) | ||||||
|                 fragment = new SubscriptionDetailFragment(); |                 fragment = new SubscriptionDetailFragment(); | ||||||
|             else |             else | ||||||
|                 throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " |                 throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + | ||||||
|  |                         "was " | ||||||
|                         + item.getClass().getSimpleName()); |                         + item.getClass().getSimpleName()); | ||||||
|             fragment.setArguments(arguments); |             fragment.setArguments(arguments); | ||||||
|             getSupportFragmentManager().beginTransaction() |             getSupportFragmentManager().beginTransaction() | ||||||
| @@ -396,7 +404,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|             else if (item instanceof BitmessageAddress) |             else if (item instanceof BitmessageAddress) | ||||||
|                 detailIntent = new Intent(this, SubscriptionDetailActivity.class); |                 detailIntent = new Intent(this, SubscriptionDetailActivity.class); | ||||||
|             else |             else | ||||||
|                 throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " |                 throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + | ||||||
|  |                         "was " | ||||||
|                         + item.getClass().getSimpleName()); |                         + item.getClass().getSimpleName()); | ||||||
| 
 | 
 | ||||||
|             detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); |             detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); | ||||||
| @@ -417,7 +426,8 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|     @Override |     @Override | ||||||
|     protected void onStart() { |     protected void onStart() { | ||||||
|         super.onStart(); |         super.onStart(); | ||||||
|         bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE); |         bindService(new Intent(this, BitmessageService.class), connection, Context | ||||||
|  |                 .BIND_AUTO_CREATE); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -459,8 +469,10 @@ public class MessageListActivity extends AppCompatActivity | |||||||
|                                 .withEmail(identity.getAddress()) |                                 .withEmail(identity.getAddress()) | ||||||
|                                 .withTag(identity); |                                 .withTag(identity); | ||||||
|                         if (accountHeader.getProfiles() != null) { |                         if (accountHeader.getProfiles() != null) { | ||||||
|                             //we know that there are 2 setting elements. set the new profile above them ;) |                             //we know that there are 2 setting elements. set the new profile | ||||||
|                             accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2); |                             // above them ;) | ||||||
|  |                             accountHeader.addProfile(newProfile, accountHeader.getProfiles().size | ||||||
|  |                                     () - 2); | ||||||
|                         } else { |                         } else { | ||||||
|                             accountHeader.addProfiles(newProfile); |                             accountHeader.addProfiles(newProfile); | ||||||
|                         } |                         } | ||||||
| @@ -3,7 +3,6 @@ package ch.dissem.apps.abit; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.v4.app.NavUtils; | import android.support.v4.app.NavUtils; | ||||||
| import android.support.v7.app.ActionBarActivity; |  | ||||||
| import android.support.v7.app.AppCompatActivity; | import android.support.v7.app.AppCompatActivity; | ||||||
| import android.support.v7.widget.Toolbar; | import android.support.v7.widget.Toolbar; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| @@ -13,7 +12,7 @@ import android.view.MenuItem; | |||||||
|  * An activity representing a single Message detail screen. This |  * An activity representing a single Message detail screen. This | ||||||
|  * activity is only used on handset devices. On tablet-size devices, |  * activity is only used on handset devices. On tablet-size devices, | ||||||
|  * item details are presented side-by-side with a list of items |  * item details are presented side-by-side with a list of items | ||||||
|  * in a {@link MessageListActivity}. |  * in a {@link MainActivity}. | ||||||
|  * <p/> |  * <p/> | ||||||
|  * This activity is mostly just a 'shell' activity containing nothing |  * This activity is mostly just a 'shell' activity containing nothing | ||||||
|  * more than a {@link MessageDetailFragment}. |  * more than a {@link MessageDetailFragment}. | ||||||
| @@ -64,7 +63,7 @@ public class MessageDetailActivity extends AppCompatActivity { | |||||||
|             // |             // | ||||||
|             // http://developer.android.com/design/patterns/navigation.html#up-vs-back |             // http://developer.android.com/design/patterns/navigation.html#up-vs-back | ||||||
|             // |             // | ||||||
|             NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class)); |             NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         return super.onOptionsItemSelected(item); |         return super.onOptionsItemSelected(item); | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import android.os.Bundle; | |||||||
| import android.support.v4.app.Fragment; | import android.support.v4.app.Fragment; | ||||||
| import android.text.util.Linkify; | import android.text.util.Linkify; | ||||||
| import android.text.util.Linkify.TransformFilter; | import android.text.util.Linkify.TransformFilter; | ||||||
| import android.util.Patterns; |  | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -19,7 +18,6 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial; | |||||||
|  |  | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.regex.Matcher; | import java.util.regex.Matcher; | ||||||
| import java.util.regex.Pattern; |  | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.service.Singleton; | import ch.dissem.apps.abit.service.Singleton; | ||||||
| import ch.dissem.apps.abit.util.Drawables; | import ch.dissem.apps.abit.util.Drawables; | ||||||
| @@ -28,8 +26,6 @@ import ch.dissem.bitmessage.entity.Plaintext; | |||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; | import ch.dissem.bitmessage.entity.valueobject.Label; | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; | import ch.dissem.bitmessage.ports.MessageRepository; | ||||||
|  |  | ||||||
| import static android.text.util.Linkify.ALL; |  | ||||||
| import static android.text.util.Linkify.EMAIL_ADDRESSES; |  | ||||||
| import static android.text.util.Linkify.WEB_URLS; | import static android.text.util.Linkify.WEB_URLS; | ||||||
| import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN; | import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN; | ||||||
| import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA; | import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA; | ||||||
| @@ -37,7 +33,7 @@ import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA; | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A fragment representing a single Message detail screen. |  * A fragment representing a single Message detail screen. | ||||||
|  * This fragment is either contained in a {@link MessageListActivity} |  * This fragment is either contained in a {@link MainActivity} | ||||||
|  * in two-pane mode (on tablets) or a {@link MessageDetailActivity} |  * in two-pane mode (on tablets) or a {@link MessageDetailActivity} | ||||||
|  * on handsets. |  * on handsets. | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| package ch.dissem.apps.abit; | package ch.dissem.apps.abit; | ||||||
|  |  | ||||||
| import android.annotation.SuppressLint; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.graphics.Typeface; | import android.graphics.Typeface; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| @@ -55,7 +53,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> { | |||||||
|     public void onResume() { |     public void onResume() { | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|  |  | ||||||
|         updateList(((MessageListActivity) getActivity()).getSelectedLabel()); |         doUpdateList(((MainActivity) getActivity()).getSelectedLabel()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -64,6 +62,10 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> { | |||||||
|  |  | ||||||
|         if (!isVisible()) return; |         if (!isVisible()) return; | ||||||
|  |  | ||||||
|  |         doUpdateList(label); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void doUpdateList(Label label) { | ||||||
|         setListAdapter(new ArrayAdapter<Plaintext>( |         setListAdapter(new ArrayAdapter<Plaintext>( | ||||||
|                 getActivity(), |                 getActivity(), | ||||||
|                 android.R.layout.simple_list_item_activated_1, |                 android.R.layout.simple_list_item_activated_1, | ||||||
| @@ -114,7 +116,7 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> { | |||||||
|             @Override |             @Override | ||||||
|             public void onClick(View view) { |             public void onClick(View view) { | ||||||
|                 Intent intent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class); |                 Intent intent = new Intent(getActivity().getApplicationContext(), ComposeMessageActivity.class); | ||||||
|                 intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MessageListActivity)getActivity()).getSelectedIdentity()); |                 intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, ((MainActivity) getActivity()).getSelectedIdentity()); | ||||||
|                 startActivity(intent); |                 startActivity(intent); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -43,7 +43,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; | |||||||
| import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS; | import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS; | ||||||
| import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT; | import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT; | ||||||
| import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE; | import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE; | ||||||
| import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT; |  | ||||||
|  |  | ||||||
| public class OpenBitmessageLinkActivity extends AppCompatActivity { | public class OpenBitmessageLinkActivity extends AppCompatActivity { | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class); |     private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class); | ||||||
| @@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|                 final int what; |                 final int what; | ||||||
|                 if (subscribe.isChecked()) |                 if (subscribe.isChecked()) | ||||||
|                     what = MSG_SUBSCRIBE_AND_ADD_CONTACT; |                     what = MSG_SUBSCRIBE; | ||||||
|                 else |                 else | ||||||
|                     what = MSG_ADD_CONTACT; |                     what = MSG_ADD_CONTACT; | ||||||
|  |  | ||||||
| @@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity { | |||||||
|     @Override |     @Override | ||||||
|     protected void onStart() { |     protected void onStart() { | ||||||
|         super.onStart(); |         super.onStart(); | ||||||
|         bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE); |         bindService(new Intent(this, BitmessageService.class), connection, Context | ||||||
|  |                 .BIND_AUTO_CREATE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity; | |||||||
| import android.support.v7.widget.Toolbar; | import android.support.v7.widget.Toolbar; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Created by chris on 14.07.15. |  * @author Christian Basler | ||||||
|  */ |  */ | ||||||
| public class SettingsActivity extends AppCompatActivity { | public class SettingsActivity extends AppCompatActivity { | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -1,13 +1,22 @@ | |||||||
| package ch.dissem.apps.abit; | package ch.dissem.apps.abit; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.SharedPreferences; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceActivity; |  | ||||||
| import android.preference.PreferenceFragment; | import android.preference.PreferenceFragment; | ||||||
|  | import android.preference.PreferenceManager; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.synchronization.SyncAdapter; | ||||||
|  |  | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW; | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Created by chris on 14.07.15. |  * @author Christian Basler | ||||||
|  */ |  */ | ||||||
| public class SettingsFragment extends PreferenceFragment { | public class SettingsFragment | ||||||
|  |         extends PreferenceFragment | ||||||
|  |         implements SharedPreferences.OnSharedPreferenceChangeListener { | ||||||
|     @Override |     @Override | ||||||
|     public void onCreate(Bundle savedInstanceState) { |     public void onCreate(Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
| @@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment { | |||||||
|         // Load the preferences from an XML resource |         // Load the preferences from an XML resource | ||||||
|         addPreferencesFromResource(R.xml.preferences); |         addPreferencesFromResource(R.xml.preferences); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onAttach(Context ctx) { | ||||||
|  |         super.onAttach(ctx); | ||||||
|  |         PreferenceManager.getDefaultSharedPreferences(ctx) | ||||||
|  |                 .registerOnSharedPreferenceChangeListener(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { | ||||||
|  |         switch (key) { | ||||||
|  |             case PREFERENCE_TRUSTED_NODE: | ||||||
|  |                 String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null); | ||||||
|  |                 if (node != null) { | ||||||
|  |                     SyncAdapter.startSync(getActivity()); | ||||||
|  |                 } else { | ||||||
|  |                     SyncAdapter.stopSync(getActivity()); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case PREFERENCE_SERVER_POW: | ||||||
|  |                 if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) { | ||||||
|  |                     SyncAdapter.startPowSync(getActivity()); | ||||||
|  |                 } else { | ||||||
|  |                     SyncAdapter.stopPowSync(getActivity()); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -28,7 +28,7 @@ import android.view.MenuItem; | |||||||
|  * An activity representing a single Subscription detail screen. This |  * An activity representing a single Subscription detail screen. This | ||||||
|  * activity is only used on handset devices. On tablet-size devices, |  * activity is only used on handset devices. On tablet-size devices, | ||||||
|  * item details are presented side-by-side with a list of items |  * item details are presented side-by-side with a list of items | ||||||
|  * in a {@link MessageListActivity}. |  * in a {@link MainActivity}. | ||||||
|  * <p/> |  * <p/> | ||||||
|  * This activity is mostly just a 'shell' activity containing nothing |  * This activity is mostly just a 'shell' activity containing nothing | ||||||
|  * more than a {@link SubscriptionDetailFragment}. |  * more than a {@link SubscriptionDetailFragment}. | ||||||
| @@ -79,7 +79,7 @@ public class SubscriptionDetailActivity extends AppCompatActivity { | |||||||
|             // |             // | ||||||
|             // http://developer.android.com/design/patterns/navigation.html#up-vs-back |             // http://developer.android.com/design/patterns/navigation.html#up-vs-back | ||||||
|             // |             // | ||||||
|             NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class)); |             NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class)); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         return super.onOptionsItemSelected(item); |         return super.onOptionsItemSelected(item); | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ import ch.dissem.bitmessage.entity.BitmessageAddress; | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A fragment representing a single Message detail screen. |  * A fragment representing a single Message detail screen. | ||||||
|  * This fragment is either contained in a {@link MessageListActivity} |  * This fragment is either contained in a {@link MainActivity} | ||||||
|  * in two-pane mode (on tablets) or a {@link MessageDetailActivity} |  * in two-pane mode (on tablets) or a {@link MessageDetailActivity} | ||||||
|  * on handsets. |  * on handsets. | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| package ch.dissem.apps.abit; | package ch.dissem.apps.abit; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -25,6 +26,7 @@ import android.widget.ArrayAdapter; | |||||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.listener.ActionBarListener; | ||||||
| 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; | ||||||
| @@ -100,12 +102,18 @@ public class SubscriptionListFragment extends AbstractItemListFragment<Bitmessag | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onAttach(Context ctx) { | ||||||
|  |         super.onAttach(ctx); | ||||||
|  |         if (ctx instanceof ActionBarListener){ | ||||||
|  |             ((ActionBarListener) ctx).updateTitle(getString(R.string.contacts_and_subscriptions)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nullable |     @Nullable | ||||||
|     @Override |     @Override | ||||||
|     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | ||||||
|         View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false); |         return inflater.inflate(R.layout.fragment_contact_list, container, false); | ||||||
|  |  | ||||||
|         return rootView; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | package ch.dissem.apps.abit.adapter; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | import android.preference.PreferenceManager; | ||||||
|  |  | ||||||
|  | import java.io.ByteArrayOutputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.util.PRNGFixes; | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
|  | import ch.dissem.bitmessage.entity.ObjectMessage; | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext; | ||||||
|  | import ch.dissem.bitmessage.entity.PlaintextHolder; | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Broadcast; | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.Label; | ||||||
|  | import ch.dissem.bitmessage.factory.Factory; | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkEngine; | ||||||
|  | import ch.dissem.bitmessage.security.sc.SpongySecurity; | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime; | ||||||
|  |  | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW; | ||||||
|  | import static ch.dissem.bitmessage.entity.Plaintext.Status.SENT; | ||||||
|  | import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; | ||||||
|  | import static ch.dissem.bitmessage.utils.UnixTime.DAY; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | public class AndroidSecurity extends SpongySecurity { | ||||||
|  |     public AndroidSecurity() { | ||||||
|  |         PRNGFixes.apply(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | package ch.dissem.apps.abit.adapter; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | import android.preference.PreferenceManager; | ||||||
|  |  | ||||||
|  | import java.util.Arrays; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.util.Preferences; | ||||||
|  | import ch.dissem.bitmessage.InternalContext; | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkEngine; | ||||||
|  |  | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Switches between two {@link ProofOfWorkEngine}s depending on the configuration. | ||||||
|  |  * | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder { | ||||||
|  |     private final Context ctx; | ||||||
|  |     private final String preference; | ||||||
|  |     private final ProofOfWorkEngine option; | ||||||
|  |     private final ProofOfWorkEngine fallback; | ||||||
|  |  | ||||||
|  |     public SwitchingProofOfWorkEngine(Context ctx, String preference, | ||||||
|  |                                       ProofOfWorkEngine option, ProofOfWorkEngine fallback) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         this.preference = preference; | ||||||
|  |         this.option = option; | ||||||
|  |         this.fallback = fallback; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) { | ||||||
|  |         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); | ||||||
|  |         if (preferences.getBoolean(preference, false)) { | ||||||
|  |             option.calculateNonce(initialHash, target, callback); | ||||||
|  |         } else { | ||||||
|  |             fallback.calculateNonce(initialHash, target, callback); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void setContext(InternalContext context) { | ||||||
|  |         for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) { | ||||||
|  |             if (e instanceof InternalContext.ContextHolder) { | ||||||
|  |                 ((InternalContext.ContextHolder) e).setContext(context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -18,25 +18,13 @@ package ch.dissem.apps.abit.listener; | |||||||
|  |  | ||||||
| import android.annotation.TargetApi; | import android.annotation.TargetApi; | ||||||
| import android.app.NotificationManager; | import android.app.NotificationManager; | ||||||
| import android.app.PendingIntent; |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; |  | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
| import android.graphics.Bitmap; |  | ||||||
| import android.graphics.Canvas; |  | ||||||
| import android.graphics.Typeface; |  | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.provider.ContactsContract; | import android.provider.ContactsContract; | ||||||
| import android.support.v7.app.NotificationCompat; | import android.support.v7.app.NotificationCompat; | ||||||
| import android.text.Spannable; |  | ||||||
| import android.text.SpannableString; |  | ||||||
| import android.text.Spanned; |  | ||||||
| import android.text.style.StyleSpan; |  | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.Identicon; |  | ||||||
| import ch.dissem.apps.abit.MessageListActivity; |  | ||||||
| import ch.dissem.apps.abit.R; |  | ||||||
| import ch.dissem.apps.abit.notification.NewMessageNotification; | import ch.dissem.apps.abit.notification.NewMessageNotification; | ||||||
| import ch.dissem.bitmessage.BitmessageContext; | import ch.dissem.bitmessage.BitmessageContext; | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; | import ch.dissem.bitmessage.entity.Plaintext; | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import android.support.v7.app.NotificationCompat; | |||||||
| import java.util.Timer; | import java.util.Timer; | ||||||
| import java.util.TimerTask; | import java.util.TimerTask; | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.MessageListActivity; | import ch.dissem.apps.abit.MainActivity; | ||||||
| import ch.dissem.apps.abit.R; | import ch.dissem.apps.abit.R; | ||||||
| import ch.dissem.bitmessage.BitmessageContext; | import ch.dissem.bitmessage.BitmessageContext; | ||||||
| import ch.dissem.bitmessage.utils.Property; | import ch.dissem.bitmessage.utils.Property; | ||||||
| @@ -64,7 +64,7 @@ public class NetworkNotification extends AbstractNotification { | |||||||
|             } |             } | ||||||
|             builder.setContentText(info); |             builder.setContentText(info); | ||||||
|         } |         } | ||||||
|         Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); |         Intent showMessageIntent = new Intent(ctx, MainActivity.class); | ||||||
|         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0); |         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showMessageIntent, 0); | ||||||
|         builder.setContentIntent(pendingIntent); |         builder.setContentIntent(pendingIntent); | ||||||
|         notification = builder.build(); |         notification = builder.build(); | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import android.text.style.StyleSpan; | |||||||
| import java.util.LinkedList; | import java.util.LinkedList; | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.Identicon; | import ch.dissem.apps.abit.Identicon; | ||||||
| import ch.dissem.apps.abit.MessageListActivity; | import ch.dissem.apps.abit.MainActivity; | ||||||
| import ch.dissem.apps.abit.R; | import ch.dissem.apps.abit.R; | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; | import ch.dissem.bitmessage.entity.Plaintext; | ||||||
|  |  | ||||||
| @@ -38,8 +38,8 @@ public class NewMessageNotification extends AbstractNotification { | |||||||
|                 .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)) |                 .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)) | ||||||
|                 .setContentInfo("Info"); |                 .setContentInfo("Info"); | ||||||
|  |  | ||||||
|         Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); |         Intent showMessageIntent = new Intent(ctx, MainActivity.class); | ||||||
|         showMessageIntent.putExtra(MessageListActivity.EXTRA_SHOW_MESSAGE, plaintext); |         showMessageIntent.putExtra(MainActivity.EXTRA_SHOW_MESSAGE, plaintext); | ||||||
|         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); |         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|         builder.setContentIntent(pendingIntent); |         builder.setContentIntent(pendingIntent); | ||||||
|  |  | ||||||
| @@ -66,8 +66,8 @@ public class NewMessageNotification extends AbstractNotification { | |||||||
|         } |         } | ||||||
|         builder.setStyle(inboxStyle); |         builder.setStyle(inboxStyle); | ||||||
|  |  | ||||||
|         Intent intent = new Intent(ctx, MessageListActivity.class); |         Intent intent = new Intent(ctx, MainActivity.class); | ||||||
|         intent.setAction(MessageListActivity.ACTION_SHOW_INBOX); |         intent.setAction(MainActivity.ACTION_SHOW_INBOX); | ||||||
|         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0); |         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0); | ||||||
|         builder.setContentIntent(pendingIntent); |         builder.setContentIntent(pendingIntent); | ||||||
|         notification = builder.build(); |         notification = builder.build(); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import android.content.Context; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.support.v7.app.NotificationCompat; | import android.support.v7.app.NotificationCompat; | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.MessageListActivity; | import ch.dissem.apps.abit.MainActivity; | ||||||
| import ch.dissem.apps.abit.R; | import ch.dissem.apps.abit.R; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -18,7 +18,7 @@ public class ProofOfWorkNotification extends AbstractNotification { | |||||||
|         super(ctx); |         super(ctx); | ||||||
|         NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); |         NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); | ||||||
|  |  | ||||||
|         Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); |         Intent showMessageIntent = new Intent(ctx, MainActivity.class); | ||||||
|         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); |         PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|  |  | ||||||
|         builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) |         builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | ||||||
|   | |||||||
| @@ -0,0 +1,81 @@ | |||||||
|  | package ch.dissem.apps.abit.pow; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.support.annotation.NonNull; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.ExecutorService; | ||||||
|  | import java.util.concurrent.Executors; | ||||||
|  | import java.util.concurrent.ThreadFactory; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.service.Singleton; | ||||||
|  | import ch.dissem.apps.abit.synchronization.SyncAdapter; | ||||||
|  | import ch.dissem.apps.abit.util.Preferences; | ||||||
|  | import ch.dissem.bitmessage.InternalContext; | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
|  | import ch.dissem.bitmessage.extensions.CryptoCustomMessage; | ||||||
|  | import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkEngine; | ||||||
|  |  | ||||||
|  | import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE; | ||||||
|  | import static ch.dissem.bitmessage.utils.Singleton.security; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | public class ServerPowEngine implements ProofOfWorkEngine, InternalContext | ||||||
|  |         .ContextHolder { | ||||||
|  |     private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class); | ||||||
|  |  | ||||||
|  |     private final Context ctx; | ||||||
|  |     private InternalContext context; | ||||||
|  |  | ||||||
|  |     private final ExecutorService pool; | ||||||
|  |  | ||||||
|  |     public ServerPowEngine(Context ctx) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         pool = Executors.newCachedThreadPool(new ThreadFactory() { | ||||||
|  |             @Override | ||||||
|  |             public Thread newThread(@NonNull Runnable r) { | ||||||
|  |                 Thread thread = Executors.defaultThreadFactory().newThread(r); | ||||||
|  |                 thread.setPriority(Thread.MIN_PRIORITY); | ||||||
|  |                 return thread; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) { | ||||||
|  |         pool.execute(new Runnable() { | ||||||
|  |             @Override | ||||||
|  |             public void run() { | ||||||
|  |                 BitmessageAddress identity = Singleton.getIdentity(ctx); | ||||||
|  |                 if (identity == null) throw new RuntimeException("No Identity for calculating POW"); | ||||||
|  |  | ||||||
|  |                 ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash, | ||||||
|  |                         CALCULATE, target); | ||||||
|  |                 SyncAdapter.startPowSync(ctx); | ||||||
|  |                 try { | ||||||
|  |                     CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<> | ||||||
|  |                             (request); | ||||||
|  |                     cryptoMsg.signAndEncrypt( | ||||||
|  |                             identity, | ||||||
|  |                             security().createPublicKey(identity.getPublicDecryptionKey()) | ||||||
|  |                     ); | ||||||
|  |                     context.getNetworkHandler().send( | ||||||
|  |                             Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx), | ||||||
|  |                             cryptoMsg); | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     LOG.error(e.getMessage(), e); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void setContext(InternalContext context) { | ||||||
|  |         this.context = context; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -19,6 +19,7 @@ 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 ch.dissem.bitmessage.entity.BitmessageAddress; | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; | import ch.dissem.bitmessage.entity.payload.Pubkey; | ||||||
| import ch.dissem.bitmessage.entity.payload.V3Pubkey; | import ch.dissem.bitmessage.entity.payload.V3Pubkey; | ||||||
| @@ -27,6 +28,7 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey; | |||||||
| import ch.dissem.bitmessage.factory.Factory; | import ch.dissem.bitmessage.factory.Factory; | ||||||
| import ch.dissem.bitmessage.ports.AddressRepository; | import ch.dissem.bitmessage.ports.AddressRepository; | ||||||
| import ch.dissem.bitmessage.utils.Encode; | import ch.dissem.bitmessage.utils.Encode; | ||||||
|  |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| @@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository { | |||||||
|                 COLUMN_SUBSCRIBED |                 COLUMN_SUBSCRIBED | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|  |         Cursor c = db.query( | ||||||
|  |                 TABLE_NAME, projection, | ||||||
|  |                 where, | ||||||
|  |                 null, null, null, null | ||||||
|  |         ); | ||||||
|         try { |         try { | ||||||
|             SQLiteDatabase db = sql.getReadableDatabase(); |  | ||||||
|             Cursor c = db.query( |  | ||||||
|                     TABLE_NAME, projection, |  | ||||||
|                     where, |  | ||||||
|                     null, null, null, null |  | ||||||
|             ); |  | ||||||
|             c.moveToFirst(); |             c.moveToFirst(); | ||||||
|             while (!c.isAfterLast()) { |             while (!c.isAfterLast()) { | ||||||
|                 BitmessageAddress address; |                 BitmessageAddress address; | ||||||
|  |  | ||||||
|                 byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); |                 byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); | ||||||
|                 if (privateKeyBytes != null) { |                 if (privateKeyBytes != null) { | ||||||
|                     PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes)); |                     PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream | ||||||
|  |                             (privateKeyBytes)); | ||||||
|                     address = new BitmessageAddress(privateKey); |                     address = new BitmessageAddress(privateKey); | ||||||
|                 } else { |                 } else { | ||||||
|                     address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS))); |                     address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS))); | ||||||
|                     byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY)); |                     byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY)); | ||||||
|                     if (publicKeyBytes != null) { |                     if (publicKeyBytes != null) { | ||||||
|                         Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), |                         Pubkey pubkey = Factory.readPubkey(address.getVersion(), address | ||||||
|                                 new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false); |                                         .getStream(), | ||||||
|  |                                 new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, | ||||||
|  |                                 false); | ||||||
|                         if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { |                         if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { | ||||||
|                             pubkey = new V4Pubkey((V3Pubkey) pubkey); |                             pubkey = new V4Pubkey((V3Pubkey) pubkey); | ||||||
|                         } |                         } | ||||||
| @@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository { | |||||||
|             } |             } | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|             LOG.error(e.getMessage(), e); |             LOG.error(e.getMessage(), e); | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository { | |||||||
|  |  | ||||||
|     private boolean exists(BitmessageAddress address) { |     private boolean exists(BitmessageAddress address) { | ||||||
|         SQLiteDatabase db = sql.getReadableDatabase(); |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|         Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null); |         Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address | ||||||
|         cursor.moveToFirst(); |                 .getAddress() + "'", null); | ||||||
|         return cursor.getInt(0) > 0; |         try { | ||||||
|  |             cursor.moveToFirst(); | ||||||
|  |             return cursor.getInt(0) > 0; | ||||||
|  |         } finally { | ||||||
|  |             cursor.close(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void update(BitmessageAddress address) throws IOException { |     private void update(BitmessageAddress address) throws IOException { | ||||||
| @@ -194,7 +205,8 @@ public class AndroidAddressRepository implements AddressRepository { | |||||||
|             values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); |             values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey())); | ||||||
|             values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); |             values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); | ||||||
|  |  | ||||||
|             int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + "'", null); |             int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + | ||||||
|  |                     "'", null); | ||||||
|             if (update < 0) { |             if (update < 0) { | ||||||
|                 LOG.error("Could not update address " + address); |                 LOG.error("Could not update address " + address); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -74,15 +74,20 @@ public class AndroidInventory implements Inventory { | |||||||
|         SQLiteDatabase db = sql.getReadableDatabase(); |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|         Cursor c = db.query( |         Cursor c = db.query( | ||||||
|                 TABLE_NAME, projection, |                 TABLE_NAME, projection, | ||||||
|                 (includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")", |                 (includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join | ||||||
|  |                         (streams) + ")", | ||||||
|                 null, null, null, null |                 null, null, null, null | ||||||
|         ); |         ); | ||||||
|         c.moveToFirst(); |  | ||||||
|         List<InventoryVector> result = new LinkedList<>(); |         List<InventoryVector> result = new LinkedList<>(); | ||||||
|         while (!c.isAfterLast()) { |         try { | ||||||
|             byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); |             c.moveToFirst(); | ||||||
|             result.add(new InventoryVector(blob)); |             while (!c.isAfterLast()) { | ||||||
|             c.moveToNext(); |                 byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); | ||||||
|  |                 result.add(new InventoryVector(blob)); | ||||||
|  |                 c.moveToNext(); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @@ -108,15 +113,19 @@ public class AndroidInventory implements Inventory { | |||||||
|                 "hash = X'" + vector + "'", |                 "hash = X'" + vector + "'", | ||||||
|                 null, null, null, null |                 null, null, null, null | ||||||
|         ); |         ); | ||||||
|         c.moveToFirst(); |         try { | ||||||
|         if (c.isAfterLast()) { |             c.moveToFirst(); | ||||||
|             LOG.info("Object requested that we don't have. IV: " + vector); |             if (c.isAfterLast()) { | ||||||
|             return null; |                 LOG.info("Object requested that we don't have. IV: " + vector); | ||||||
|         } |                 return null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|         int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); |             int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); | ||||||
|         byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); |             byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); | ||||||
|         return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length); |             return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length); | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -144,13 +153,18 @@ public class AndroidInventory implements Inventory { | |||||||
|                 where.toString(), |                 where.toString(), | ||||||
|                 null, null, null, null |                 null, null, null, null | ||||||
|         ); |         ); | ||||||
|         c.moveToFirst(); |  | ||||||
|         List<ObjectMessage> result = new LinkedList<>(); |         List<ObjectMessage> result = new LinkedList<>(); | ||||||
|         while (!c.isAfterLast()) { |         try { | ||||||
|             int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); |             c.moveToFirst(); | ||||||
|             byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); |             while (!c.isAfterLast()) { | ||||||
|             result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length)); |                 int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); | ||||||
|             c.moveToNext(); |                 byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); | ||||||
|  |                 result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), | ||||||
|  |                         blob.length)); | ||||||
|  |                 c.moveToNext(); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory { | |||||||
|                 "hash = X'" + object.getInventoryVector() + "'", |                 "hash = X'" + object.getInventoryVector() + "'", | ||||||
|                 null, null, null, null |                 null, null, null, null | ||||||
|         ); |         ); | ||||||
|         return c.getCount() > 0; |         try { | ||||||
|  |             return c.getCount() > 0; | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector; | |||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; | import ch.dissem.bitmessage.entity.valueobject.Label; | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; | import ch.dissem.bitmessage.ports.MessageRepository; | ||||||
| import ch.dissem.bitmessage.utils.Encode; | import ch.dissem.bitmessage.utils.Encode; | ||||||
|  | import ch.dissem.bitmessage.utils.Strings; | ||||||
|  |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| @@ -58,6 +59,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|     private static final String COLUMN_SENT = "sent"; |     private static final String COLUMN_SENT = "sent"; | ||||||
|     private static final String COLUMN_RECEIVED = "received"; |     private static final String COLUMN_RECEIVED = "received"; | ||||||
|     private static final String COLUMN_STATUS = "status"; |     private static final String COLUMN_STATUS = "status"; | ||||||
|  |     private static final String COLUMN_INITIAL_HASH = "initial_hash"; | ||||||
|  |  | ||||||
|     private static final String JOIN_TABLE_NAME = "Message_Label"; |     private static final String JOIN_TABLE_NAME = "Message_Label"; | ||||||
|     private static final String JT_COLUMN_MESSAGE = "message_id"; |     private static final String JT_COLUMN_MESSAGE = "message_id"; | ||||||
| @@ -112,10 +114,14 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|                 null, null, null, |                 null, null, null, | ||||||
|                 LBL_COLUMN_ORDER |                 LBL_COLUMN_ORDER | ||||||
|         ); |         ); | ||||||
|         c.moveToFirst(); |         try { | ||||||
|         while (!c.isAfterLast()) { |             c.moveToFirst(); | ||||||
|             result.add(getLabel(c)); |             while (!c.isAfterLast()) { | ||||||
|             c.moveToNext(); |                 result.add(getLabel(c)); | ||||||
|  |                 c.moveToNext(); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @@ -124,27 +130,31 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|         String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE)); |         String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE)); | ||||||
|         Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName); |         Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName); | ||||||
|         String text; |         String text; | ||||||
|         switch (type) { |         if (type == null) { | ||||||
|             case INBOX: |             text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL)); | ||||||
|                 text = ctx.getString(R.string.inbox); |         } else { | ||||||
|                 break; |             switch (type) { | ||||||
|             case DRAFT: |                 case INBOX: | ||||||
|                 text = ctx.getString(R.string.draft); |                     text = ctx.getString(R.string.inbox); | ||||||
|                 break; |                     break; | ||||||
|             case SENT: |                 case DRAFT: | ||||||
|                 text = ctx.getString(R.string.sent); |                     text = ctx.getString(R.string.draft); | ||||||
|                 break; |                     break; | ||||||
|             case UNREAD: |                 case SENT: | ||||||
|                 text = ctx.getString(R.string.unread); |                     text = ctx.getString(R.string.sent); | ||||||
|                 break; |                     break; | ||||||
|             case TRASH: |                 case UNREAD: | ||||||
|                 text = ctx.getString(R.string.trash); |                     text = ctx.getString(R.string.unread); | ||||||
|                 break; |                     break; | ||||||
|             case BROADCAST: |                 case TRASH: | ||||||
|                 text = ctx.getString(R.string.broadcasts); |                     text = ctx.getString(R.string.trash); | ||||||
|                 break; |                     break; | ||||||
|             default: |                 case BROADCAST: | ||||||
|                 text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL)); |                     text = ctx.getString(R.string.broadcasts); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL)); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         Label label = new Label( |         Label label = new Label( | ||||||
|                 text, |                 text, | ||||||
| @@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|     public int countUnread(Label label) { |     public int countUnread(Label label) { | ||||||
|         String where; |         String where; | ||||||
|         if (label != null) { |         if (label != null) { | ||||||
|             where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND "; |             where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() | ||||||
|  |                     + ") AND "; | ||||||
|         } else { |         } else { | ||||||
|             where = ""; |             where = ""; | ||||||
|         } |         } | ||||||
| @@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|                         "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))", |                         "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))", | ||||||
|                 null, null, null, null |                 null, null, null, null | ||||||
|         ); |         ); | ||||||
|         return c.getColumnCount(); |         try { | ||||||
|  |             return c.getColumnCount(); | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Plaintext getMessage(byte[] initialHash) { | ||||||
|  |         List<Plaintext> results = find("initial_hash=X'" + Strings.hex(initialHash) + "'"); | ||||||
|  |         switch (results.size()) { | ||||||
|  |             case 0: | ||||||
|  |                 return null; | ||||||
|  |             case 1: | ||||||
|  |                 return results.get(0); | ||||||
|  |             default: | ||||||
|  |                 throw new RuntimeException("This shouldn't happen, found " + results.size() + | ||||||
|  |                         " messages, one or none was expected"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<Plaintext> findMessages(Label label) { |     public List<Plaintext> findMessages(Label label) { | ||||||
|         if (label != null) { |         if (label != null) { | ||||||
|             return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"); |             return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label | ||||||
|  |                     .getId() + ")"); | ||||||
|         } else { |         } else { | ||||||
|             return find("id NOT IN (SELECT message_id FROM Message_Label)"); |             return find("id NOT IN (SELECT message_id FROM Message_Label)"); | ||||||
|         } |         } | ||||||
| @@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { |     public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { | ||||||
|         return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'"); |         return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + | ||||||
|  |                 "'"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -213,34 +244,41 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|                 COLUMN_STATUS |                 COLUMN_STATUS | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|  |         Cursor c = db.query( | ||||||
|  |                 TABLE_NAME, projection, | ||||||
|  |                 where, | ||||||
|  |                 null, null, null, | ||||||
|  |                 COLUMN_RECEIVED + " DESC" | ||||||
|  |         ); | ||||||
|         try { |         try { | ||||||
|             SQLiteDatabase db = sql.getReadableDatabase(); |  | ||||||
|             Cursor c = db.query( |  | ||||||
|                     TABLE_NAME, projection, |  | ||||||
|                     where, |  | ||||||
|                     null, null, null, |  | ||||||
|                     COLUMN_RECEIVED + " DESC" |  | ||||||
|             ); |  | ||||||
|             c.moveToFirst(); |             c.moveToFirst(); | ||||||
|             while (!c.isAfterLast()) { |             while (!c.isAfterLast()) { | ||||||
|                 byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV)); |                 byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV)); | ||||||
|                 byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); |                 byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); | ||||||
|                 Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE))); |                 Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex | ||||||
|                 Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data)); |                         (COLUMN_TYPE))); | ||||||
|  |                 Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new | ||||||
|  |                         ByteArrayInputStream(data)); | ||||||
|                 long id = c.getLong(c.getColumnIndex(COLUMN_ID)); |                 long id = c.getLong(c.getColumnIndex(COLUMN_ID)); | ||||||
|                 builder.id(id); |                 builder.id(id); | ||||||
|                 builder.IV(new InventoryVector(iv)); |                 builder.IV(new InventoryVector(iv)); | ||||||
|                 builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER)))); |                 builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex | ||||||
|                 builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT)))); |                         (COLUMN_SENDER)))); | ||||||
|  |                 builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex | ||||||
|  |                         (COLUMN_RECIPIENT)))); | ||||||
|                 builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT))); |                 builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT))); | ||||||
|                 builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED))); |                 builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED))); | ||||||
|                 builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex(COLUMN_STATUS)))); |                 builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex | ||||||
|  |                         (COLUMN_STATUS)))); | ||||||
|                 builder.labels(findLabels(id)); |                 builder.labels(findLabels(id)); | ||||||
|                 result.add(builder.build()); |                 result.add(builder.build()); | ||||||
|                 c.moveToNext(); |                 c.moveToNext(); | ||||||
|             } |             } | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|             LOG.error(e.getMessage(), e); |             LOG.error(e.getMessage(), e); | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|         } |         } | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| @@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|  |  | ||||||
|             // save from address if necessary |             // save from address if necessary | ||||||
|             if (message.getId() == null) { |             if (message.getId() == null) { | ||||||
|                 BitmessageAddress savedAddress = bmc.getAddressRepo().getAddress(message.getFrom().getAddress()); |                 BitmessageAddress savedAddress = bmc.getAddressRepository().getAddress(message | ||||||
|  |                         .getFrom().getAddress()); | ||||||
|                 if (savedAddress == null || savedAddress.getPrivateKey() == null) { |                 if (savedAddress == null || savedAddress.getPrivateKey() == null) { | ||||||
|                     if (savedAddress != null && savedAddress.getAlias() != null) { |                     if (savedAddress != null && savedAddress.getAlias() != null) { | ||||||
|                         message.getFrom().setAlias(savedAddress.getAlias()); |                         message.getFrom().setAlias(savedAddress.getAlias()); | ||||||
|                     } |                     } | ||||||
|                     bmc.getAddressRepo().save(message.getFrom()); |                     bmc.getAddressRepository().save(message.getFrom()); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -295,7 +334,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|  |  | ||||||
|     private void insert(SQLiteDatabase db, Plaintext message) throws IOException { |     private void insert(SQLiteDatabase db, Plaintext message) throws IOException { | ||||||
|         ContentValues values = new ContentValues(); |         ContentValues values = new ContentValues(); | ||||||
|         values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); |         values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message | ||||||
|  |                 .getInventoryVector().getHash()); | ||||||
|         values.put(COLUMN_TYPE, message.getType().name()); |         values.put(COLUMN_TYPE, message.getType().name()); | ||||||
|         values.put(COLUMN_SENDER, message.getFrom().getAddress()); |         values.put(COLUMN_SENDER, message.getFrom().getAddress()); | ||||||
|         values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); |         values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); | ||||||
| @@ -303,13 +343,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|         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() == null ? null : message.getStatus().name()); | ||||||
|  |         values.put(COLUMN_INITIAL_HASH, message.getInitialHash()); | ||||||
|         long id = db.insertOrThrow(TABLE_NAME, null, values); |         long id = db.insertOrThrow(TABLE_NAME, null, values); | ||||||
|         message.setId(id); |         message.setId(id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void update(SQLiteDatabase db, Plaintext message) throws IOException { |     private void update(SQLiteDatabase db, Plaintext message) throws IOException { | ||||||
|         ContentValues values = new ContentValues(); |         ContentValues values = new ContentValues(); | ||||||
|         values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); |         values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message | ||||||
|  |                 .getInventoryVector().getHash()); | ||||||
|         values.put(COLUMN_TYPE, message.getType().name()); |         values.put(COLUMN_TYPE, message.getType().name()); | ||||||
|         values.put(COLUMN_SENDER, message.getFrom().getAddress()); |         values.put(COLUMN_SENDER, message.getFrom().getAddress()); | ||||||
|         values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); |         values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); | ||||||
| @@ -317,6 +359,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont | |||||||
|         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() == null ? null : message.getStatus().name()); | ||||||
|  |         values.put(COLUMN_INITIAL_HASH, message.getInitialHash()); | ||||||
|         db.update(TABLE_NAME, values, "id = " + message.getId(), null); |         db.update(TABLE_NAME, values, "id = " + message.getId(), null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,133 @@ | |||||||
|  | package ch.dissem.apps.abit.repository; | ||||||
|  |  | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.database.sqlite.SQLiteConstraintException; | ||||||
|  | import android.database.sqlite.SQLiteDatabase; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.io.ByteArrayInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.LinkedList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.ObjectMessage; | ||||||
|  | import ch.dissem.bitmessage.factory.Factory; | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkRepository; | ||||||
|  | import ch.dissem.bitmessage.utils.Encode; | ||||||
|  | import ch.dissem.bitmessage.utils.Strings; | ||||||
|  |  | ||||||
|  | import static ch.dissem.bitmessage.utils.Singleton.security; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | public class AndroidProofOfWorkRepository implements ProofOfWorkRepository { | ||||||
|  |     private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class); | ||||||
|  |  | ||||||
|  |     private static final String TABLE_NAME = "POW"; | ||||||
|  |     private static final String COLUMN_INITIAL_HASH = "initial_hash"; | ||||||
|  |     private static final String COLUMN_DATA = "data"; | ||||||
|  |     private static final String COLUMN_VERSION = "version"; | ||||||
|  |     private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte"; | ||||||
|  |     private static final String COLUMN_EXTRA_BYTES = "extra_bytes"; | ||||||
|  |  | ||||||
|  |     private final SqlHelper sql; | ||||||
|  |  | ||||||
|  |     public AndroidProofOfWorkRepository(SqlHelper sql) { | ||||||
|  |         this.sql = sql; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Item getItem(byte[] initialHash) { | ||||||
|  |         // Define a projection that specifies which columns from the database | ||||||
|  |         // you will actually use after this query. | ||||||
|  |         String[] projection = { | ||||||
|  |                 COLUMN_DATA, | ||||||
|  |                 COLUMN_VERSION, | ||||||
|  |                 COLUMN_NONCE_TRIALS_PER_BYTE, | ||||||
|  |                 COLUMN_EXTRA_BYTES | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|  |         Cursor c = db.query( | ||||||
|  |                 TABLE_NAME, projection, | ||||||
|  |                 "initial_hash = X'" + Strings.hex(initialHash) + "'", | ||||||
|  |                 null, null, null, null | ||||||
|  |         ); | ||||||
|  |         try { | ||||||
|  |             c.moveToFirst(); | ||||||
|  |             if (!c.isAfterLast()) { | ||||||
|  |                 int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); | ||||||
|  |                 byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); | ||||||
|  |                 return new Item( | ||||||
|  |                         Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob | ||||||
|  |                                 .length), | ||||||
|  |                         c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), | ||||||
|  |                         c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)) | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|  |         throw new RuntimeException("Object requested that we don't have. Initial hash: " + | ||||||
|  |                 Strings.hex(initialHash)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<byte[]> getItems() { | ||||||
|  |         // Define a projection that specifies which columns from the database | ||||||
|  |         // you will actually use after this query. | ||||||
|  |         String[] projection = { | ||||||
|  |                 COLUMN_INITIAL_HASH | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         SQLiteDatabase db = sql.getReadableDatabase(); | ||||||
|  |         Cursor c = db.query( | ||||||
|  |                 TABLE_NAME, projection, | ||||||
|  |                 null, null, null, null, null | ||||||
|  |         ); | ||||||
|  |         List<byte[]> result = new LinkedList<>(); | ||||||
|  |         try { | ||||||
|  |             c.moveToFirst(); | ||||||
|  |             while (!c.isAfterLast()) { | ||||||
|  |                 byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH)); | ||||||
|  |                 result.add(initialHash); | ||||||
|  |                 c.moveToNext(); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { | ||||||
|  |         try { | ||||||
|  |             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, security().getInitialHash(object)); | ||||||
|  |             values.put(COLUMN_DATA, Encode.bytes(object)); | ||||||
|  |             values.put(COLUMN_VERSION, object.getVersion()); | ||||||
|  |             values.put(COLUMN_NONCE_TRIALS_PER_BYTE, nonceTrialsPerByte); | ||||||
|  |             values.put(COLUMN_EXTRA_BYTES, extraBytes); | ||||||
|  |  | ||||||
|  |             db.insertOrThrow(TABLE_NAME, null, values); | ||||||
|  |         } catch (SQLiteConstraintException e) { | ||||||
|  |             LOG.trace(e.getMessage(), e); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             LOG.error(e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void removeObject(byte[] initialHash) { | ||||||
|  |         SQLiteDatabase db = sql.getWritableDatabase(); | ||||||
|  |         db.delete(TABLE_NAME, | ||||||
|  |                 "initial_hash = X'" + Strings.hex(initialHash) + "'", | ||||||
|  |                 null); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets; | |||||||
|  */ |  */ | ||||||
| public class SqlHelper extends SQLiteOpenHelper { | public class SqlHelper extends SQLiteOpenHelper { | ||||||
|     // If you change the database schema, you must increment the database version. |     // If you change the database schema, you must increment the database version. | ||||||
|     public static final int DATABASE_VERSION = 1; |     public static final int DATABASE_VERSION = 2; | ||||||
|     public static final String DATABASE_NAME = "jabit.db"; |     public static final String DATABASE_NAME = "jabit.db"; | ||||||
|  |  | ||||||
|     protected final Context ctx; |     protected final Context ctx; | ||||||
| @@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onCreate(SQLiteDatabase db) { |     public void onCreate(SQLiteDatabase db) { | ||||||
|         onUpgrade(db, 0, 1); |         onUpgrade(db, 0, 2); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper { | |||||||
|                 executeMigration(db, "V1.0__Create_table_inventory"); |                 executeMigration(db, "V1.0__Create_table_inventory"); | ||||||
|                 executeMigration(db, "V1.1__Create_table_address"); |                 executeMigration(db, "V1.1__Create_table_address"); | ||||||
|                 executeMigration(db, "V1.2__Create_table_message"); |                 executeMigration(db, "V1.2__Create_table_message"); | ||||||
|  |             case 1: | ||||||
|  |                 // executeMigration(db, "V2.0__Update_table_message"); | ||||||
|  |                 executeMigration(db, "V2.1__Create_table_POW"); | ||||||
|             default: |             default: | ||||||
|                 // Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. |                 // Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -34,7 +34,6 @@ public class BitmessageService extends Service { | |||||||
|     public static final int MSG_CREATE_IDENTITY = 10; |     public static final int MSG_CREATE_IDENTITY = 10; | ||||||
|     public static final int MSG_SUBSCRIBE = 20; |     public static final int MSG_SUBSCRIBE = 20; | ||||||
|     public static final int MSG_ADD_CONTACT = 21; |     public static final int MSG_ADD_CONTACT = 21; | ||||||
|     public static final int MSG_SUBSCRIBE_AND_ADD_CONTACT = 23; |  | ||||||
|     public static final int MSG_SEND_MESSAGE = 30; |     public static final int MSG_SEND_MESSAGE = 30; | ||||||
|     public static final int MSG_SEND_BROADCAST = 31; |     public static final int MSG_SEND_BROADCAST = 31; | ||||||
|     public static final int MSG_START_NODE = 100; |     public static final int MSG_START_NODE = 100; | ||||||
| @@ -122,6 +121,13 @@ public class BitmessageService extends Service { | |||||||
|                     } |                     } | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|  |                 case MSG_ADD_CONTACT: { | ||||||
|  |                     Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS); | ||||||
|  |                     if (data instanceof BitmessageAddress) { | ||||||
|  |                         bmc.addContact((BitmessageAddress) data); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|                 case MSG_SEND_MESSAGE: { |                 case MSG_SEND_MESSAGE: { | ||||||
|                     Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY); |                     Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY); | ||||||
|                     Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS); |                     Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS); | ||||||
|   | |||||||
| @@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service { | |||||||
|             service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); |             service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); | ||||||
|             engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() { |             engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() { | ||||||
|                 @Override |                 @Override | ||||||
|                 public void onNonceCalculated(byte[] nonce) { |                 public void onNonceCalculated(byte[] initialHash, byte[] nonce) { | ||||||
|                     try { |                     try { | ||||||
|                         callback.onNonceCalculated(nonce); |                         callback.onNonceCalculated(initialHash, nonce); | ||||||
|                     } finally { |                     } finally { | ||||||
|                         service.stopForeground(true); |                         service.stopForeground(true); | ||||||
|                         service.stopSelf(); |                         service.stopSelf(); | ||||||
|   | |||||||
| @@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onNonceCalculated(byte[] bytes) { |     public void onNonceCalculated(byte[] initialHash, byte[] bytes) { | ||||||
|         callback.onNonceCalculated(bytes); |         callback.onNonceCalculated(initialHash, bytes); | ||||||
|         ctx.unbindService(connection); |         ctx.unbindService(connection); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,17 +2,27 @@ package ch.dissem.apps.abit.service; | |||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.adapter.AndroidSecurity; | ||||||
|  | import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine; | ||||||
| import ch.dissem.apps.abit.listener.MessageListener; | import ch.dissem.apps.abit.listener.MessageListener; | ||||||
|  | import ch.dissem.apps.abit.pow.ServerPowEngine; | ||||||
| import ch.dissem.apps.abit.repository.AndroidAddressRepository; | import ch.dissem.apps.abit.repository.AndroidAddressRepository; | ||||||
| import ch.dissem.apps.abit.repository.AndroidInventory; | import ch.dissem.apps.abit.repository.AndroidInventory; | ||||||
| import ch.dissem.apps.abit.repository.AndroidMessageRepository; | import ch.dissem.apps.abit.repository.AndroidMessageRepository; | ||||||
|  | import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository; | ||||||
| import ch.dissem.apps.abit.repository.SqlHelper; | import ch.dissem.apps.abit.repository.SqlHelper; | ||||||
|  | import ch.dissem.apps.abit.util.Constants; | ||||||
| import ch.dissem.bitmessage.BitmessageContext; | import ch.dissem.bitmessage.BitmessageContext; | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
| import ch.dissem.bitmessage.networking.DefaultNetworkHandler; | import ch.dissem.bitmessage.networking.DefaultNetworkHandler; | ||||||
| import ch.dissem.bitmessage.ports.AddressRepository; | import ch.dissem.bitmessage.ports.AddressRepository; | ||||||
| import ch.dissem.bitmessage.ports.MemoryNodeRegistry; | import ch.dissem.bitmessage.ports.MemoryNodeRegistry; | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; | import ch.dissem.bitmessage.ports.MessageRepository; | ||||||
| import ch.dissem.bitmessage.security.sc.SpongySecurity; | import ch.dissem.bitmessage.ports.ProofOfWorkRepository; | ||||||
|  |  | ||||||
|  | import static ch.dissem.bitmessage.utils.UnixTime.DAY; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Provides singleton objects across the application. |  * Provides singleton objects across the application. | ||||||
| @@ -21,6 +31,8 @@ public class Singleton { | |||||||
|     public static final Object lock = new Object(); |     public static final Object lock = new Object(); | ||||||
|     private static BitmessageContext bitmessageContext; |     private static BitmessageContext bitmessageContext; | ||||||
|     private static MessageListener messageListener; |     private static MessageListener messageListener; | ||||||
|  |     private static BitmessageAddress identity; | ||||||
|  |     private static AndroidProofOfWorkRepository powRepo; | ||||||
|  |  | ||||||
|     public static BitmessageContext getBitmessageContext(Context context) { |     public static BitmessageContext getBitmessageContext(Context context) { | ||||||
|         if (bitmessageContext == null) { |         if (bitmessageContext == null) { | ||||||
| @@ -28,15 +40,23 @@ public class Singleton { | |||||||
|                 if (bitmessageContext == null) { |                 if (bitmessageContext == null) { | ||||||
|                     final Context ctx = context.getApplicationContext(); |                     final Context ctx = context.getApplicationContext(); | ||||||
|                     SqlHelper sqlHelper = new SqlHelper(ctx); |                     SqlHelper sqlHelper = new SqlHelper(ctx); | ||||||
|  |                     powRepo = new AndroidProofOfWorkRepository(sqlHelper); | ||||||
|                     bitmessageContext = new BitmessageContext.Builder() |                     bitmessageContext = new BitmessageContext.Builder() | ||||||
|                             .proofOfWorkEngine(new ServicePowEngine(ctx)) |                             .proofOfWorkEngine(new SwitchingProofOfWorkEngine( | ||||||
|                             .security(new SpongySecurity()) |                                     ctx, Constants.PREFERENCE_SERVER_POW, | ||||||
|  |                                     new ServerPowEngine(ctx), | ||||||
|  |                                     new ServicePowEngine(ctx) | ||||||
|  |                             )) | ||||||
|  |                             .security(new AndroidSecurity()) | ||||||
|                             .nodeRegistry(new MemoryNodeRegistry()) |                             .nodeRegistry(new MemoryNodeRegistry()) | ||||||
|                             .inventory(new AndroidInventory(sqlHelper)) |                             .inventory(new AndroidInventory(sqlHelper)) | ||||||
|                             .addressRepo(new AndroidAddressRepository(sqlHelper)) |                             .addressRepo(new AndroidAddressRepository(sqlHelper)) | ||||||
|                             .messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) |                             .messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) | ||||||
|  |                             .powRepo(powRepo) | ||||||
|                             .networkHandler(new DefaultNetworkHandler()) |                             .networkHandler(new DefaultNetworkHandler()) | ||||||
|                             .listener(getMessageListener(ctx)) |                             .listener(getMessageListener(ctx)) | ||||||
|  |                             .doNotSendPubkeyOnIdentityCreation() | ||||||
|  |                             .pubkeyTTL(2 * DAY) | ||||||
|                             .build(); |                             .build(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -62,4 +82,24 @@ public class Singleton { | |||||||
|     public static AddressRepository getAddressRepository(Context ctx) { |     public static AddressRepository getAddressRepository(Context ctx) { | ||||||
|         return getBitmessageContext(ctx).addresses(); |         return getBitmessageContext(ctx).addresses(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) { | ||||||
|  |         if (powRepo == null) getBitmessageContext(ctx); | ||||||
|  |         return powRepo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static BitmessageAddress getIdentity(Context ctx) { | ||||||
|  |         if (identity == null) { | ||||||
|  |             synchronized (Singleton.class) { | ||||||
|  |                 if (identity == null) { | ||||||
|  |                     List<BitmessageAddress> identities = getBitmessageContext(ctx).addresses() | ||||||
|  |                             .getIdentities(); | ||||||
|  |                     if (identities.size() > 0) { | ||||||
|  |                         identity = identities.get(0); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return identity; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,8 +12,8 @@ import android.os.Bundle; | |||||||
|  * of its methods |  * of its methods | ||||||
|  */ |  */ | ||||||
| public class Authenticator extends AbstractAccountAuthenticator { | public class Authenticator extends AbstractAccountAuthenticator { | ||||||
|     public static final String ACCOUNT_NAME = "Bitmessage"; |     public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage"); | ||||||
|     public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage"; |     public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage"); | ||||||
|  |  | ||||||
|     // Simple constructor |     // Simple constructor | ||||||
|     public Authenticator(Context context) { |     public Authenticator(Context context) { | ||||||
|   | |||||||
| @@ -1,24 +1,34 @@ | |||||||
| package ch.dissem.apps.abit.synchronization; | package ch.dissem.apps.abit.synchronization; | ||||||
|  |  | ||||||
| import android.accounts.Account; | import android.accounts.Account; | ||||||
|  | import android.accounts.AccountManager; | ||||||
| import android.content.AbstractThreadedSyncAdapter; | import android.content.AbstractThreadedSyncAdapter; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
|  | import android.content.ContentResolver; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.SharedPreferences; |  | ||||||
| import android.content.SyncResult; | import android.content.SyncResult; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; |  | ||||||
|  |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import java.net.InetAddress; | import java.util.List; | ||||||
| import java.net.UnknownHostException; |  | ||||||
|  |  | ||||||
| import ch.dissem.apps.abit.R; |  | ||||||
| import ch.dissem.apps.abit.notification.ErrorNotification; |  | ||||||
| import ch.dissem.apps.abit.service.Singleton; | import ch.dissem.apps.abit.service.Singleton; | ||||||
|  | import ch.dissem.apps.abit.util.Preferences; | ||||||
| import ch.dissem.bitmessage.BitmessageContext; | import ch.dissem.bitmessage.BitmessageContext; | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||||
|  | import ch.dissem.bitmessage.entity.CustomMessage; | ||||||
|  | import ch.dissem.bitmessage.extensions.CryptoCustomMessage; | ||||||
|  | import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkRepository; | ||||||
|  |  | ||||||
|  | import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW; | ||||||
|  | import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC; | ||||||
|  | import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY; | ||||||
|  | import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE; | ||||||
|  | import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE; | ||||||
|  | import static ch.dissem.bitmessage.utils.Singleton.security; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Sync Adapter to synchronize with the Bitmessage network - fetches |  * Sync Adapter to synchronize with the Bitmessage network - fetches | ||||||
| @@ -27,6 +37,8 @@ import ch.dissem.bitmessage.BitmessageContext; | |||||||
| public class SyncAdapter extends AbstractThreadedSyncAdapter { | public class SyncAdapter extends AbstractThreadedSyncAdapter { | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class); |     private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class); | ||||||
|  |  | ||||||
|  |     private static final long SYNC_FREQUENCY = 15 * 60; // seconds | ||||||
|  |  | ||||||
|     private final BitmessageContext bmc; |     private final BitmessageContext bmc; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -38,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { |     public void onPerformSync(Account account, Bundle extras, String authority, | ||||||
|  |                               ContentProviderClient provider, SyncResult syncResult) { | ||||||
|  |         if (account.equals(Authenticator.ACCOUNT_SYNC)) | ||||||
|  |             syncData(); | ||||||
|  |         else if (account.equals(Authenticator.ACCOUNT_POW)) | ||||||
|  |             syncPOW(); | ||||||
|  |         else | ||||||
|  |             throw new RuntimeException("Unknown " + account); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void syncData() { | ||||||
|         // If the Bitmessage context acts as a full node, synchronization isn't necessary |         // If the Bitmessage context acts as a full node, synchronization isn't necessary | ||||||
|         if (bmc.isRunning()) { |         if (bmc.isRunning()) { | ||||||
|             LOG.info("Synchronization skipped, Abit is acting as a full node"); |             LOG.info("Synchronization skipped, Abit is acting as a full node"); | ||||||
| @@ -46,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { | |||||||
|         } |         } | ||||||
|         LOG.info("Synchronizing Bitmessage"); |         LOG.info("Synchronizing Bitmessage"); | ||||||
|  |  | ||||||
|         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); |  | ||||||
|  |  | ||||||
|         String trustedNode = preferences.getString("trusted_node", null); |  | ||||||
|         if (trustedNode == null) return; |  | ||||||
|         trustedNode = trustedNode.trim(); |  | ||||||
|         if (trustedNode.isEmpty()) return; |  | ||||||
|  |  | ||||||
|         int port; |  | ||||||
|         if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) { |  | ||||||
|             int index = trustedNode.lastIndexOf(':'); |  | ||||||
|             String portString = trustedNode.substring(index + 1); |  | ||||||
|             trustedNode = trustedNode.substring(0, index); |  | ||||||
|             try { |  | ||||||
|                 port = Integer.parseInt(portString); |  | ||||||
|             } catch (NumberFormatException e) { |  | ||||||
|                 new ErrorNotification(getContext()) |  | ||||||
|                         .setError(R.string.error_invalid_sync_port, portString) |  | ||||||
|                         .show(); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             port = 8444; |  | ||||||
|         } |  | ||||||
|         long timeoutInSeconds = Long.parseLong(preferences.getString("sync_timeout", "120")); |  | ||||||
|         try { |         try { | ||||||
|             LOG.info("Synchronization started"); |             LOG.info("Synchronization started"); | ||||||
|             bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true); |             bmc.synchronize( | ||||||
|  |                     Preferences.getTrustedNode(getContext()), | ||||||
|  |                     Preferences.getTrustedNodePort(getContext()), | ||||||
|  |                     Preferences.getTimeoutInSeconds(getContext()), | ||||||
|  |                     true); | ||||||
|             LOG.info("Synchronization finished"); |             LOG.info("Synchronization finished"); | ||||||
|         } catch (UnknownHostException e) { |  | ||||||
|             new ErrorNotification(getContext()) |  | ||||||
|                     .setError(R.string.error_invalid_sync_host) |  | ||||||
|                     .show(); |  | ||||||
|         } catch (RuntimeException e) { |         } catch (RuntimeException e) { | ||||||
|             LOG.error(e.getMessage(), e); |             LOG.error(e.getMessage(), e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void syncPOW() { | ||||||
|  |         // If the Bitmessage context acts as a full node, synchronization isn't necessary | ||||||
|  |         LOG.info("Looking for completed POW"); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             BitmessageAddress identity = Singleton.getIdentity(getContext()); | ||||||
|  |             byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey(); | ||||||
|  |             byte[] signingKey = security().createPublicKey(identity.getPublicDecryptionKey()); | ||||||
|  |             ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity); | ||||||
|  |             ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext()); | ||||||
|  |             List<byte[]> items = powRepo.getItems(); | ||||||
|  |             for (byte[] initialHash : items) { | ||||||
|  |                 ProofOfWorkRepository.Item item = powRepo.getItem(initialHash); | ||||||
|  |                 byte[] target = security().getProofOfWorkTarget(item.object, item | ||||||
|  |                         .nonceTrialsPerByte, item.extraBytes); | ||||||
|  |                 CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>( | ||||||
|  |                         new ProofOfWorkRequest(identity, initialHash, CALCULATE, target)); | ||||||
|  |                 cryptoMsg.signAndEncrypt(identity, signingKey); | ||||||
|  |                 CustomMessage response = bmc.send( | ||||||
|  |                         Preferences.getTrustedNode(getContext()), | ||||||
|  |                         Preferences.getTrustedNodePort(getContext()), | ||||||
|  |                         cryptoMsg | ||||||
|  |                 ); | ||||||
|  |                 if (response.isError()) { | ||||||
|  |                     LOG.error("Server responded with error: " + new String(response.getData(), | ||||||
|  |                             "UTF-8")); | ||||||
|  |                 } else { | ||||||
|  |                     ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read( | ||||||
|  |                             response, reader).decrypt(privateKey); | ||||||
|  |                     if (decryptedResponse.getRequest() == COMPLETE) { | ||||||
|  |                         bmc.internals().getProofOfWorkService().onNonceCalculated( | ||||||
|  |                                 initialHash, decryptedResponse.getData()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (items.size() == 0) { | ||||||
|  |                 stopPowSync(getContext()); | ||||||
|  |             } | ||||||
|  |             LOG.info("Synchronization finished"); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             LOG.error(e.getMessage(), e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void startSync(Context ctx) { | ||||||
|  |         // Create account, if it's missing. (Either first run, or user has deleted account.) | ||||||
|  |         Account account = addAccount(ctx, ACCOUNT_SYNC); | ||||||
|  |  | ||||||
|  |         // Recommend a schedule for automatic synchronization. The system may modify this based | ||||||
|  |         // on other scheduled syncs and network utilization. | ||||||
|  |         ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void stopSync(Context ctx) { | ||||||
|  |         // Create account, if it's missing. (Either first run, or user has deleted account.) | ||||||
|  |         Account account = addAccount(ctx, ACCOUNT_SYNC); | ||||||
|  |  | ||||||
|  |         ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public static void startPowSync(Context ctx) { | ||||||
|  |         // Create account, if it's missing. (Either first run, or user has deleted account.) | ||||||
|  |         Account account = addAccount(ctx, ACCOUNT_POW); | ||||||
|  |  | ||||||
|  |         // Recommend a schedule for automatic synchronization. The system may modify this based | ||||||
|  |         // on other scheduled syncs and network utilization. | ||||||
|  |         ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static void stopPowSync(Context ctx) { | ||||||
|  |         // Create account, if it's missing. (Either first run, or user has deleted account.) | ||||||
|  |         Account account = addAccount(ctx, ACCOUNT_POW); | ||||||
|  |  | ||||||
|  |         ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static Account addAccount(Context ctx, Account account) { | ||||||
|  |         if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) { | ||||||
|  |             // Inform the system that this account supports sync | ||||||
|  |             ContentResolver.setIsSyncable(account, AUTHORITY, 1); | ||||||
|  |             // Inform the system that this account is eligible for auto sync when the network is up | ||||||
|  |             ContentResolver.setSyncAutomatically(account, AUTHORITY, true); | ||||||
|  |         } | ||||||
|  |         return account; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,9 +3,14 @@ package ch.dissem.apps.abit.util; | |||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Created by chrigu on 16.11.15. |  * @author Christian Basler | ||||||
|  */ |  */ | ||||||
| public class Constants { | public class Constants { | ||||||
|  |     public static final String PREFERENCE_WIFI_ONLY = "wifi_only"; | ||||||
|  |     public static final String PREFERENCE_TRUSTED_NODE = "trusted_node"; | ||||||
|  |     public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout"; | ||||||
|  |     public static final String PREFERENCE_SERVER_POW = "server_pow"; | ||||||
|  |  | ||||||
|     public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:"; |     public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:"; | ||||||
|     public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b"); |     public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b"); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										341
									
								
								app/src/main/java/ch/dissem/apps/abit/util/PRNGFixes.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								app/src/main/java/ch/dissem/apps/abit/util/PRNGFixes.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,341 @@ | |||||||
|  | package ch.dissem.apps.abit.util; | ||||||
|  | /* | ||||||
|  |  * This software is provided 'as-is', without any express or implied | ||||||
|  |  * warranty.  In no event will Google be held liable for any damages | ||||||
|  |  * arising from the use of this software. | ||||||
|  |  * | ||||||
|  |  * Permission is granted to anyone to use this software for any purpose, | ||||||
|  |  * including commercial applications, and to alter it and redistribute it | ||||||
|  |  * freely, as long as the origin is not misrepresented. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import android.os.Build; | ||||||
|  | import android.os.Process; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
|  | import java.io.ByteArrayOutputStream; | ||||||
|  | import java.io.DataInputStream; | ||||||
|  | import java.io.DataOutputStream; | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.FileInputStream; | ||||||
|  | import java.io.FileOutputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.OutputStream; | ||||||
|  | import java.io.UnsupportedEncodingException; | ||||||
|  | import java.security.NoSuchAlgorithmException; | ||||||
|  | import java.security.Provider; | ||||||
|  | import java.security.SecureRandom; | ||||||
|  | import java.security.SecureRandomSpi; | ||||||
|  | import java.security.Security; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Fixes for the output of the default PRNG having low entropy. | ||||||
|  |  * <p/> | ||||||
|  |  * The fixes need to be applied via {@link #apply()} before any use of Java | ||||||
|  |  * Cryptography Architecture primitives. A good place to invoke them is in the | ||||||
|  |  * application's {@code onCreate}. | ||||||
|  |  * | ||||||
|  |  * @see <a href="http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html"> | ||||||
|  |  * http://android-developers.blogspot.ch/2013/08/some-securerandom-thoughts.html</a> | ||||||
|  |  */ | ||||||
|  | public final class PRNGFixes { | ||||||
|  |  | ||||||
|  |     private static final int VERSION_CODE_JELLY_BEAN = 16; | ||||||
|  |     private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; | ||||||
|  |     private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = | ||||||
|  |             getBuildFingerprintAndDeviceSerial(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Hidden constructor to prevent instantiation. | ||||||
|  |      */ | ||||||
|  |     private PRNGFixes() { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Applies all fixes. | ||||||
|  |      * | ||||||
|  |      * @throws SecurityException if a fix is needed but could not be applied. | ||||||
|  |      */ | ||||||
|  |     public static void apply() { | ||||||
|  |         applyOpenSSLFix(); | ||||||
|  |         installLinuxPRNGSecureRandom(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the | ||||||
|  |      * fix is not needed. | ||||||
|  |      * | ||||||
|  |      * @throws SecurityException if the fix is needed but could not be applied. | ||||||
|  |      */ | ||||||
|  |     private static void applyOpenSSLFix() throws SecurityException { | ||||||
|  |         if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) | ||||||
|  |                 || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { | ||||||
|  |             // No need to apply the fix | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             // Mix in the device- and invocation-specific seed. | ||||||
|  |             Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") | ||||||
|  |                     .getMethod("RAND_seed", byte[].class) | ||||||
|  |                     .invoke(null, generateSeed()); | ||||||
|  |  | ||||||
|  |             // Mix output of Linux PRNG into OpenSSL's PRNG | ||||||
|  |             int bytesRead = (Integer) Class.forName( | ||||||
|  |                     "org.apache.harmony.xnet.provider.jsse.NativeCrypto") | ||||||
|  |                     .getMethod("RAND_load_file", String.class, long.class) | ||||||
|  |                     .invoke(null, "/dev/urandom", 1024); | ||||||
|  |             if (bytesRead != 1024) { | ||||||
|  |                 throw new IOException( | ||||||
|  |                         "Unexpected number of bytes read from Linux PRNG: " | ||||||
|  |                                 + bytesRead); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             throw new SecurityException("Failed to seed OpenSSL PRNG", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the | ||||||
|  |      * default. Does nothing if the implementation is already the default or if | ||||||
|  |      * there is not need to install the implementation. | ||||||
|  |      * | ||||||
|  |      * @throws SecurityException if the fix is needed but could not be applied. | ||||||
|  |      */ | ||||||
|  |     private static void installLinuxPRNGSecureRandom() | ||||||
|  |             throws SecurityException { | ||||||
|  |         if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { | ||||||
|  |             // No need to apply the fix | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Install a Linux PRNG-based SecureRandom implementation as the | ||||||
|  |         // default, if not yet installed. | ||||||
|  |         Provider[] secureRandomProviders = | ||||||
|  |                 Security.getProviders("SecureRandom.SHA1PRNG"); | ||||||
|  |         if ((secureRandomProviders == null) | ||||||
|  |                 || (secureRandomProviders.length < 1) | ||||||
|  |                 || (!LinuxPRNGSecureRandomProvider.class.equals( | ||||||
|  |                 secureRandomProviders[0].getClass()))) { | ||||||
|  |             Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Assert that new SecureRandom() and | ||||||
|  |         // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed | ||||||
|  |         // by the Linux PRNG-based SecureRandom implementation. | ||||||
|  |         SecureRandom rng1 = new SecureRandom(); | ||||||
|  |         if (!LinuxPRNGSecureRandomProvider.class.equals( | ||||||
|  |                 rng1.getProvider().getClass())) { | ||||||
|  |             throw new SecurityException( | ||||||
|  |                     "new SecureRandom() backed by wrong Provider: " | ||||||
|  |                             + rng1.getProvider().getClass()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecureRandom rng2; | ||||||
|  |         try { | ||||||
|  |             rng2 = SecureRandom.getInstance("SHA1PRNG"); | ||||||
|  |         } catch (NoSuchAlgorithmException e) { | ||||||
|  |             throw new SecurityException("SHA1PRNG not available", e); | ||||||
|  |         } | ||||||
|  |         if (!LinuxPRNGSecureRandomProvider.class.equals( | ||||||
|  |                 rng2.getProvider().getClass())) { | ||||||
|  |             throw new SecurityException( | ||||||
|  |                     "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" | ||||||
|  |                             + " Provider: " + rng2.getProvider().getClass()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * {@code Provider} of {@code SecureRandom} engines which pass through | ||||||
|  |      * all requests to the Linux PRNG. | ||||||
|  |      */ | ||||||
|  |     private static class LinuxPRNGSecureRandomProvider extends Provider { | ||||||
|  |  | ||||||
|  |         public LinuxPRNGSecureRandomProvider() { | ||||||
|  |             super("LinuxPRNG", | ||||||
|  |                     1.0, | ||||||
|  |                     "A Linux-specific random number provider that uses" | ||||||
|  |                             + " /dev/urandom"); | ||||||
|  |             // Although /dev/urandom is not a SHA-1 PRNG, some apps | ||||||
|  |             // explicitly request a SHA1PRNG SecureRandom and we thus need to | ||||||
|  |             // prevent them from getting the default implementation whose output | ||||||
|  |             // may have low entropy. | ||||||
|  |             put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); | ||||||
|  |             put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * {@link SecureRandomSpi} which passes all requests to the Linux PRNG | ||||||
|  |      * ({@code /dev/urandom}). | ||||||
|  |      */ | ||||||
|  |     public static class LinuxPRNGSecureRandom extends SecureRandomSpi { | ||||||
|  |  | ||||||
|  |         /* | ||||||
|  |          * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed | ||||||
|  |          * are passed through to the Linux PRNG (/dev/urandom). Instances of | ||||||
|  |          * this class seed themselves by mixing in the current time, PID, UID, | ||||||
|  |          * build fingerprint, and hardware serial number (where available) into | ||||||
|  |          * Linux PRNG. | ||||||
|  |          * | ||||||
|  |          * Concurrency: Read requests to the underlying Linux PRNG are | ||||||
|  |          * serialized (on sLock) to ensure that multiple threads do not get | ||||||
|  |          * duplicated PRNG output. | ||||||
|  |          */ | ||||||
|  |  | ||||||
|  |         private static final File URANDOM_FILE = new File("/dev/urandom"); | ||||||
|  |  | ||||||
|  |         private static final Object sLock = new Object(); | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Input stream for reading from Linux PRNG or {@code null} if not yet | ||||||
|  |          * opened. | ||||||
|  |          * | ||||||
|  |          * @GuardedBy("sLock") | ||||||
|  |          */ | ||||||
|  |         private static DataInputStream sUrandomIn; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Output stream for writing to Linux PRNG or {@code null} if not yet | ||||||
|  |          * opened. | ||||||
|  |          * | ||||||
|  |          * @GuardedBy("sLock") | ||||||
|  |          */ | ||||||
|  |         private static OutputStream sUrandomOut; | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Whether this engine instance has been seeded. This is needed because | ||||||
|  |          * each instance needs to seed itself if the client does not explicitly | ||||||
|  |          * seed it. | ||||||
|  |          */ | ||||||
|  |         private boolean mSeeded; | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         protected void engineSetSeed(byte[] bytes) { | ||||||
|  |             try { | ||||||
|  |                 OutputStream out; | ||||||
|  |                 synchronized (sLock) { | ||||||
|  |                     out = getUrandomOutputStream(); | ||||||
|  |                 } | ||||||
|  |                 out.write(bytes); | ||||||
|  |                 out.flush(); | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 // On a small fraction of devices /dev/urandom is not writable. | ||||||
|  |                 // Log and ignore. | ||||||
|  |                 Log.w(PRNGFixes.class.getSimpleName(), | ||||||
|  |                         "Failed to mix seed into " + URANDOM_FILE); | ||||||
|  |             } finally { | ||||||
|  |                 mSeeded = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         protected void engineNextBytes(byte[] bytes) { | ||||||
|  |             if (!mSeeded) { | ||||||
|  |                 // Mix in the device- and invocation-specific seed. | ||||||
|  |                 engineSetSeed(generateSeed()); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 DataInputStream in; | ||||||
|  |                 synchronized (sLock) { | ||||||
|  |                     in = getUrandomInputStream(); | ||||||
|  |                 } | ||||||
|  |                 synchronized (in) { | ||||||
|  |                     in.readFully(bytes); | ||||||
|  |                 } | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 throw new SecurityException( | ||||||
|  |                         "Failed to read from " + URANDOM_FILE, e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         protected byte[] engineGenerateSeed(int size) { | ||||||
|  |             byte[] seed = new byte[size]; | ||||||
|  |             engineNextBytes(seed); | ||||||
|  |             return seed; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private DataInputStream getUrandomInputStream() { | ||||||
|  |             synchronized (sLock) { | ||||||
|  |                 if (sUrandomIn == null) { | ||||||
|  |                     // NOTE: Consider inserting a BufferedInputStream between | ||||||
|  |                     // DataInputStream and FileInputStream if you need higher | ||||||
|  |                     // PRNG output performance and can live with future PRNG | ||||||
|  |                     // output being pulled into this process prematurely. | ||||||
|  |                     try { | ||||||
|  |                         sUrandomIn = new DataInputStream( | ||||||
|  |                                 new FileInputStream(URANDOM_FILE)); | ||||||
|  |                     } catch (IOException e) { | ||||||
|  |                         throw new SecurityException("Failed to open " | ||||||
|  |                                 + URANDOM_FILE + " for reading", e); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return sUrandomIn; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private OutputStream getUrandomOutputStream() throws IOException { | ||||||
|  |             synchronized (sLock) { | ||||||
|  |                 if (sUrandomOut == null) { | ||||||
|  |                     sUrandomOut = new FileOutputStream(URANDOM_FILE); | ||||||
|  |                 } | ||||||
|  |                 return sUrandomOut; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generates a device- and invocation-specific seed to be mixed into the | ||||||
|  |      * Linux PRNG. | ||||||
|  |      */ | ||||||
|  |     private static byte[] generateSeed() { | ||||||
|  |         try { | ||||||
|  |             ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); | ||||||
|  |             DataOutputStream seedBufferOut = | ||||||
|  |                     new DataOutputStream(seedBuffer); | ||||||
|  |             seedBufferOut.writeLong(System.currentTimeMillis()); | ||||||
|  |             seedBufferOut.writeLong(System.nanoTime()); | ||||||
|  |             seedBufferOut.writeInt(Process.myPid()); | ||||||
|  |             seedBufferOut.writeInt(Process.myUid()); | ||||||
|  |             seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); | ||||||
|  |             seedBufferOut.close(); | ||||||
|  |             return seedBuffer.toByteArray(); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new SecurityException("Failed to generate seed", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the hardware serial number of this device. | ||||||
|  |      * | ||||||
|  |      * @return serial number or {@code null} if not available. | ||||||
|  |      */ | ||||||
|  |     private static String getDeviceSerialNumber() { | ||||||
|  |         // We're using the Reflection API because Build.SERIAL is only available | ||||||
|  |         // since API Level 9 (Gingerbread, Android 2.3). | ||||||
|  |         try { | ||||||
|  |             return (String) Build.class.getField("SERIAL").get(null); | ||||||
|  |         } catch (Exception ignored) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static byte[] getBuildFingerprintAndDeviceSerial() { | ||||||
|  |         StringBuilder result = new StringBuilder(); | ||||||
|  |         String fingerprint = Build.FINGERPRINT; | ||||||
|  |         if (fingerprint != null) { | ||||||
|  |             result.append(fingerprint); | ||||||
|  |         } | ||||||
|  |         String serial = getDeviceSerialNumber(); | ||||||
|  |         if (serial != null) { | ||||||
|  |             result.append(serial); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|  |             return result.toString().getBytes("UTF-8"); | ||||||
|  |         } catch (UnsupportedEncodingException e) { | ||||||
|  |             throw new RuntimeException("UTF-8 encoding not supported"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | package ch.dissem.apps.abit.util; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | import android.preference.PreferenceManager; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.net.InetAddress; | ||||||
|  | import java.net.UnknownHostException; | ||||||
|  |  | ||||||
|  | import ch.dissem.apps.abit.R; | ||||||
|  | import ch.dissem.apps.abit.notification.ErrorNotification; | ||||||
|  |  | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW; | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT; | ||||||
|  | import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Created by chrig on 01.12.2015. | ||||||
|  |  */ | ||||||
|  | public class Preferences { | ||||||
|  |     private static Logger LOG = LoggerFactory.getLogger(Preferences.class); | ||||||
|  |  | ||||||
|  |     public static boolean useTrustedNode(Context ctx) { | ||||||
|  |         String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE); | ||||||
|  |         return trustedNode == null || trustedNode.trim().isEmpty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Warning, this method might do a network call and therefore can't be called from | ||||||
|  |      * the UI thread. | ||||||
|  |      */ | ||||||
|  |     public static InetAddress getTrustedNode(Context ctx) { | ||||||
|  |         String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE); | ||||||
|  |         if (trustedNode == null) return null; | ||||||
|  |         trustedNode = trustedNode.trim(); | ||||||
|  |         if (trustedNode.isEmpty()) return null; | ||||||
|  |  | ||||||
|  |         if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) { | ||||||
|  |             int index = trustedNode.lastIndexOf(':'); | ||||||
|  |             trustedNode = trustedNode.substring(0, index); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|  |             return InetAddress.getByName(trustedNode); | ||||||
|  |         } catch (UnknownHostException e) { | ||||||
|  |             new ErrorNotification(ctx) | ||||||
|  |                     .setError(R.string.error_invalid_sync_host) | ||||||
|  |                     .show(); | ||||||
|  |             LOG.error(e.getMessage(), e); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static int getTrustedNodePort(Context ctx) { | ||||||
|  |         String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE); | ||||||
|  |         if (trustedNode == null) return 8444; | ||||||
|  |         trustedNode = trustedNode.trim(); | ||||||
|  |  | ||||||
|  |         if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) { | ||||||
|  |             int index = trustedNode.lastIndexOf(':'); | ||||||
|  |             String portString = trustedNode.substring(index + 1); | ||||||
|  |             try { | ||||||
|  |                 return Integer.parseInt(portString); | ||||||
|  |             } catch (NumberFormatException e) { | ||||||
|  |                 new ErrorNotification(ctx) | ||||||
|  |                         .setError(R.string.error_invalid_sync_port, portString) | ||||||
|  |                         .show(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return 8444; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static long getTimeoutInSeconds(Context ctx) { | ||||||
|  |         String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT); | ||||||
|  |         return preference == null ? 120 : Long.parseLong(preference); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static boolean isServerPOW(Context ctx) { | ||||||
|  |         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); | ||||||
|  |         return preferences.getBoolean(PREFERENCE_SERVER_POW, false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String getPreference(Context ctx, String name) { | ||||||
|  |         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); | ||||||
|  |  | ||||||
|  |         return preferences.getString(name, null); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,21 +1,28 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:padding="16dp"> |     android:padding="24dp"> | ||||||
|  |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/address" |         android:id="@+id/address" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_alignParentLeft="true" | ||||||
|  |         android:layout_alignParentStart="true" | ||||||
|         android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" |         android:text="BM-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | ||||||
|         android:textAppearance="?android:attr/textAppearanceSmall" /> |         android:textSize="10dp" | ||||||
|  |         tools:ignore="SpUsage" /> | ||||||
|  |  | ||||||
|     <android.support.design.widget.TextInputLayout |     <android.support.design.widget.TextInputLayout | ||||||
|         android:id="@+id/label_wrapper" |         android:id="@+id/label_wrapper" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_below="@+id/address"> |         android:layout_alignLeft="@+id/address" | ||||||
|  |         android:layout_alignStart="@+id/address" | ||||||
|  |         android:layout_below="@+id/address" | ||||||
|  |         android:layout_marginTop="16dp"> | ||||||
|  |  | ||||||
|         <EditText |         <EditText | ||||||
|             android:id="@+id/label" |             android:id="@+id/label" | ||||||
| @@ -30,8 +37,9 @@ | |||||||
|         android:id="@+id/subscribe" |         android:id="@+id/subscribe" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_alignLeft="@+id/address" | ||||||
|  |         android:layout_alignStart="@+id/address" | ||||||
|         android:layout_below="@+id/label_wrapper" |         android:layout_below="@+id/label_wrapper" | ||||||
|         android:layout_centerHorizontal="true" |  | ||||||
|         android:layout_marginBottom="8dp" |         android:layout_marginBottom="8dp" | ||||||
|         android:layout_marginTop="8dp" |         android:layout_marginTop="8dp" | ||||||
|         android:text="@string/subscribe" /> |         android:text="@string/subscribe" /> | ||||||
|   | |||||||
| @@ -49,4 +49,6 @@ | |||||||
|     <string name="compose_body_hint">Nachricht schreiben</string> |     <string name="compose_body_hint">Nachricht schreiben</string> | ||||||
|     <string name="contacts_and_subscriptions">Kontakte</string> |     <string name="contacts_and_subscriptions">Kontakte</string> | ||||||
|     <string name="subscribed">Abonniert</string> |     <string name="subscribed">Abonniert</string> | ||||||
|  |     <string name="server_pow">Server POW</string> | ||||||
|  |     <string name="server_pow_summary">Der vertrauenswürdige Knoten macht den Proof of Work</string> | ||||||
| </resources> | </resources> | ||||||
| @@ -49,4 +49,6 @@ | |||||||
|     <string name="compose_body_hint">Write message</string> |     <string name="compose_body_hint">Write message</string> | ||||||
|     <string name="contacts_and_subscriptions">Contacts</string> |     <string name="contacts_and_subscriptions">Contacts</string> | ||||||
|     <string name="subscribed">Subscribed</string> |     <string name="subscribed">Subscribed</string> | ||||||
|  |     <string name="server_pow">Server POW</string> | ||||||
|  |     <string name="server_pow_summary">Trusted node does proof of work</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -24,4 +24,11 @@ | |||||||
|         android:key="sync_timeout" |         android:key="sync_timeout" | ||||||
|         android:summary="@string/sync_timeout_summary" |         android:summary="@string/sync_timeout_summary" | ||||||
|         android:title="@string/sync_timeout" /> |         android:title="@string/sync_timeout" /> | ||||||
|  |     <SwitchPreference | ||||||
|  |         android:defaultValue="false" | ||||||
|  |         android:key="server_pow" | ||||||
|  |         android:dependency="trusted_node" | ||||||
|  |         android:title="@string/server_pow" | ||||||
|  |         android:summary="@string/server_pow_summary" | ||||||
|  |         /> | ||||||
| </PreferenceScreen> | </PreferenceScreen> | ||||||
| @@ -9,7 +9,7 @@ buildscript { | |||||||
|         jcenter() |         jcenter() | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         classpath 'com.android.tools.build:gradle:1.5.0-beta1' |         classpath 'com.android.tools.build:gradle:1.5.0' | ||||||
|  |  | ||||||
|         // NOTE: Do not place your application dependencies here; they belong |         // NOTE: Do not place your application dependencies here; they belong | ||||||
|         // in the individual module build.gradle files |         // in the individual module build.gradle files | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user