Some sync adapter code and bugfixes around it - not yet functional
This commit is contained in:
		| @@ -3,9 +3,12 @@ | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     package="ch.dissem.apps.abit"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> | ||||
|     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> | ||||
|     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> | ||||
|     <uses-permission android:name="android.permission.READ_CONTACTS" /> | ||||
|     <uses-permission android:name="android.permission.WRITE_CONTACTS" /> | ||||
|  | ||||
| @@ -99,6 +102,30 @@ | ||||
|                 <category android:name="android.intent.category.BROWSABLE" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|  | ||||
|         <provider | ||||
|             android:name=".synchronization.StubProvider" | ||||
|             android:authorities="ch.dissem.bitmessage.provider" | ||||
|             android:exported="false" | ||||
|             android:syncable="true" /> | ||||
|         <service android:name=".synchronization.AuthenticatorService"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.accounts.AccountAuthenticator" /> | ||||
|             </intent-filter> | ||||
|             <meta-data | ||||
|                 android:name="android.accounts.AccountAuthenticator" | ||||
|                 android:resource="@xml/authenticator" /> | ||||
|         </service> | ||||
|         <service | ||||
|             android:name=".synchronization.SyncService" | ||||
|             android:exported="true" | ||||
|             android:process=":sync"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.content.SyncAdapter"/> | ||||
|             </intent-filter> | ||||
|             <meta-data android:name="android.content.SyncAdapter" | ||||
|                 android:resource="@xml/syncadapter" /> | ||||
|         </service> | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|   | ||||
| @@ -21,6 +21,7 @@ import android.os.Bundle; | ||||
| import android.support.v4.app.ListFragment; | ||||
| import android.view.View; | ||||
| import android.widget.ListView; | ||||
|  | ||||
| import ch.dissem.apps.abit.listeners.ListSelectionListener; | ||||
| import ch.dissem.apps.abit.service.Singleton; | ||||
| import ch.dissem.bitmessage.BitmessageContext; | ||||
| @@ -54,6 +55,7 @@ public abstract class AbstractItemListFragment<T> extends ListFragment { | ||||
|      * The current activated item position. Only used on tablets. | ||||
|      */ | ||||
|     private int activatedPosition = ListView.INVALID_POSITION; | ||||
|     private boolean activateOnItemClick; | ||||
|  | ||||
|     abstract void updateList(Label label); | ||||
|  | ||||
| @@ -75,6 +77,17 @@ public abstract class AbstractItemListFragment<T> extends ListFragment { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|  | ||||
|         // When setting CHOICE_MODE_SINGLE, ListView will automatically | ||||
|         // give items the 'activated' state when touched. | ||||
|         getListView().setChoiceMode(activateOnItemClick | ||||
|                 ? ListView.CHOICE_MODE_SINGLE | ||||
|                 : ListView.CHOICE_MODE_NONE); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onAttach(Activity activity) { | ||||
|         super.onAttach(activity); | ||||
| @@ -118,12 +131,16 @@ public abstract class AbstractItemListFragment<T> extends ListFragment { | ||||
|      * given the 'activated' state when touched. | ||||
|      */ | ||||
|     public void setActivateOnItemClick(boolean activateOnItemClick) { | ||||
|         this.activateOnItemClick = activateOnItemClick; | ||||
|  | ||||
|         if (isVisible()) { | ||||
|             // When setting CHOICE_MODE_SINGLE, ListView will automatically | ||||
|             // give items the 'activated' state when touched. | ||||
|             getListView().setChoiceMode(activateOnItemClick | ||||
|                     ? ListView.CHOICE_MODE_SINGLE | ||||
|                     : ListView.CHOICE_MODE_NONE); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void setActivatedPosition(int position) { | ||||
|         if (position == ListView.INVALID_POSITION) { | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| package ch.dissem.apps.abit; | ||||
|  | ||||
| import android.accounts.Account; | ||||
| import android.accounts.AccountManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.Fragment; | ||||
| @@ -13,6 +16,8 @@ import android.widget.AdapterView; | ||||
| import ch.dissem.apps.abit.listeners.ActionBarListener; | ||||
| import ch.dissem.apps.abit.listeners.ListSelectionListener; | ||||
| import ch.dissem.apps.abit.service.Singleton; | ||||
| import ch.dissem.apps.abit.synchronization.Authenticator; | ||||
| import ch.dissem.apps.abit.synchronization.SyncAdapter; | ||||
| import ch.dissem.bitmessage.BitmessageContext; | ||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; | ||||
| import ch.dissem.bitmessage.entity.Plaintext; | ||||
| @@ -66,6 +71,8 @@ public class MessageListActivity extends AppCompatActivity | ||||
|     private static final Logger LOG = LoggerFactory.getLogger(MessageListActivity.class); | ||||
|     private static final int ADD_IDENTITY = 1; | ||||
|  | ||||
|     private Account account; | ||||
|  | ||||
|     /** | ||||
|      * Whether or not the activity is in two-pane mode, i.e. running on a tablet | ||||
|      * device. | ||||
| @@ -97,6 +104,10 @@ public class MessageListActivity extends AppCompatActivity | ||||
|             // res/values-sw600dp). If this view is present, then the | ||||
|             // activity should be in two-pane mode. | ||||
|             twoPane = true; | ||||
|  | ||||
|             // In two-pane mode, list items should be given the | ||||
|             // 'activated' state when touched. | ||||
|             listFragment.setActivateOnItemClick(true); | ||||
|         } | ||||
|  | ||||
|         createDrawer(toolbar); | ||||
| @@ -107,17 +118,40 @@ public class MessageListActivity extends AppCompatActivity | ||||
|         if (getIntent().hasExtra(EXTRA_SHOW_MESSAGE)) { | ||||
|             onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE)); | ||||
|         } | ||||
|  | ||||
|         account = createSyncAccount(this); | ||||
|         getContentResolver().setSyncAutomatically(account, SyncAdapter.AUTHORITY, true); | ||||
|     } | ||||
|  | ||||
|     private Account createSyncAccount(Context context) { | ||||
|         // Create the account type and default account | ||||
|         Account newAccount = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE); | ||||
|         // Get an instance of the Android account manager | ||||
|         AccountManager accountManager = (AccountManager) context.getSystemService(ACCOUNT_SERVICE); | ||||
|         /* | ||||
|          * Add the account and account type, no password or user data | ||||
|          * If successful, return the Account object, otherwise report an error. | ||||
|          */ | ||||
|         if (accountManager.addAccountExplicitly(newAccount, null, null)) { | ||||
|             /* | ||||
|              * If you don't set android:syncable="true" in | ||||
|              * in your <provider> element in the manifest, | ||||
|              * then call context.setIsSyncable(account, AUTHORITY, 1) | ||||
|              * here. | ||||
|              */ | ||||
|         } else { | ||||
|             /* | ||||
|              * The account exists or some other error occurred. Log this, report it, | ||||
|              * or handle it internally. | ||||
|              */ | ||||
|             LOG.error("Couldn't add account"); | ||||
|         } | ||||
|         return newAccount; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         if (twoPane) { | ||||
|             // In two-pane mode, list items should be given the | ||||
|             // 'activated' state when touched. | ||||
|             ((MessageListFragment) getSupportFragmentManager().findFragmentById(R.id.item_list)) | ||||
|                     .setActivateOnItemClick(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void changeList(AbstractItemListFragment<?> listFragment) { | ||||
| @@ -232,7 +266,7 @@ public class MessageListActivity extends AppCompatActivity | ||||
|                         if (item.getTag() instanceof Label) { | ||||
|                             selectedLabel = (Label) item.getTag(); | ||||
|                             if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment)) { | ||||
|                                 MessageListFragment listFragment = new MessageListFragment(); | ||||
|                                 MessageListFragment listFragment = new MessageListFragment(getApplicationContext()); | ||||
|                                 changeList(listFragment); | ||||
|                                 listFragment.updateList(selectedLabel); | ||||
|                             } else { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package ch.dissem.apps.abit; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.graphics.Typeface; | ||||
| import android.os.Bundle; | ||||
| @@ -11,6 +12,7 @@ import android.widget.ArrayAdapter; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.ListView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import ch.dissem.apps.abit.listeners.ActionBarListener; | ||||
| import ch.dissem.apps.abit.listeners.ListSelectionListener; | ||||
| import ch.dissem.apps.abit.service.Singleton; | ||||
| @@ -41,6 +43,10 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> { | ||||
|     public MessageListFragment() { | ||||
|     } | ||||
|  | ||||
|     public MessageListFragment(Context ctx) { | ||||
|         bmc = Singleton.getBitmessageContext(ctx); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
| @@ -58,6 +64,9 @@ public class MessageListFragment extends AbstractItemListFragment<Plaintext> { | ||||
|     @Override | ||||
|     public void updateList(Label label) { | ||||
|         currentLabel = label; | ||||
|  | ||||
|         if (!isVisible()) return; | ||||
|  | ||||
|         setListAdapter(new ArrayAdapter<Plaintext>( | ||||
|                 getActivity(), | ||||
|                 android.R.layout.simple_list_item_activated_1, | ||||
|   | ||||
| @@ -0,0 +1,82 @@ | ||||
| package ch.dissem.apps.abit.synchronization; | ||||
|  | ||||
| import android.accounts.AbstractAccountAuthenticator; | ||||
| import android.accounts.Account; | ||||
| import android.accounts.AccountAuthenticatorResponse; | ||||
| import android.accounts.NetworkErrorException; | ||||
| import android.content.Context; | ||||
| import android.os.Bundle; | ||||
|  | ||||
| /* | ||||
|  * Implement AbstractAccountAuthenticator and stub out all | ||||
|  * of its methods | ||||
|  */ | ||||
| public class Authenticator extends AbstractAccountAuthenticator { | ||||
|     public static final String ACCOUNT_NAME = "Bitmessage"; | ||||
|     public static final String ACCOUNT_TYPE = "bitmessage.dissem.ch"; | ||||
|  | ||||
|     // Simple constructor | ||||
|     public Authenticator(Context context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     // Editing properties is not supported | ||||
|     @Override | ||||
|     public Bundle editProperties( | ||||
|             AccountAuthenticatorResponse r, String s) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
|  | ||||
|     // Don't add additional accounts | ||||
|     @Override | ||||
|     public Bundle addAccount( | ||||
|             AccountAuthenticatorResponse r, | ||||
|             String s, | ||||
|             String s2, | ||||
|             String[] strings, | ||||
|             Bundle bundle) throws NetworkErrorException { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     // Ignore attempts to confirm credentials | ||||
|     @Override | ||||
|     public Bundle confirmCredentials( | ||||
|             AccountAuthenticatorResponse r, | ||||
|             Account account, | ||||
|             Bundle bundle) throws NetworkErrorException { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     // Getting an authentication token is not supported | ||||
|     @Override | ||||
|     public Bundle getAuthToken( | ||||
|             AccountAuthenticatorResponse r, | ||||
|             Account account, | ||||
|             String s, | ||||
|             Bundle bundle) throws NetworkErrorException { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
|  | ||||
|     // Getting a label for the auth token is not supported | ||||
|     @Override | ||||
|     public String getAuthTokenLabel(String s) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
|  | ||||
|     // Updating user credentials is not supported | ||||
|     @Override | ||||
|     public Bundle updateCredentials( | ||||
|             AccountAuthenticatorResponse r, | ||||
|             Account account, | ||||
|             String s, Bundle bundle) throws NetworkErrorException { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
|  | ||||
|     // Checking features for the account is not supported | ||||
|     @Override | ||||
|     public Bundle hasFeatures( | ||||
|             AccountAuthenticatorResponse r, | ||||
|             Account account, String[] strings) throws NetworkErrorException { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package ch.dissem.apps.abit.synchronization; | ||||
|  | ||||
| import android.app.Service; | ||||
| import android.content.Intent; | ||||
| import android.os.IBinder; | ||||
|  | ||||
| /** | ||||
|  * A bound Service that instantiates the authenticator | ||||
|  * when started. | ||||
|  */ | ||||
| public class AuthenticatorService extends Service { | ||||
|     /** | ||||
|      * Instance field that stores the authenticator object | ||||
|      */ | ||||
|     private Authenticator authenticator; | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         // Create a new authenticator object | ||||
|         authenticator = new Authenticator(this); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * When the system binds to this Service to make the RPC call | ||||
|      * return the authenticator's IBinder. | ||||
|      */ | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return authenticator.getIBinder(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,65 @@ | ||||
| package ch.dissem.apps.abit.synchronization; | ||||
|  | ||||
| import android.content.ContentProvider; | ||||
| import android.content.ContentValues; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
|  | ||||
| /* | ||||
|  * Define an implementation of ContentProvider that stubs out | ||||
|  * all methods | ||||
|  */ | ||||
| public class StubProvider extends ContentProvider { | ||||
|     /* | ||||
|      * Always return true, indicating that the | ||||
|      * provider loaded correctly. | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         return true; | ||||
|     } | ||||
|     /* | ||||
|      * Return no type for MIME type | ||||
|      */ | ||||
|     @Override | ||||
|     public String getType(Uri uri) { | ||||
|         return null; | ||||
|     } | ||||
|     /* | ||||
|      * query() always returns no results | ||||
|      * | ||||
|      */ | ||||
|     @Override | ||||
|     public Cursor query( | ||||
|             Uri uri, | ||||
|             String[] projection, | ||||
|             String selection, | ||||
|             String[] selectionArgs, | ||||
|             String sortOrder) { | ||||
|         return null; | ||||
|     } | ||||
|     /* | ||||
|      * insert() always returns null (no URI) | ||||
|      */ | ||||
|     @Override | ||||
|     public Uri insert(Uri uri, ContentValues values) { | ||||
|         return null; | ||||
|     } | ||||
|     /* | ||||
|      * delete() always returns "no rows affected" (0) | ||||
|      */ | ||||
|     @Override | ||||
|     public int delete(Uri uri, String selection, String[] selectionArgs) { | ||||
|         return 0; | ||||
|     } | ||||
|     /* | ||||
|      * update() always returns "no rows affected" (0) | ||||
|      */ | ||||
|     public int update( | ||||
|             Uri uri, | ||||
|             ContentValues values, | ||||
|             String selection, | ||||
|             String[] selectionArgs) { | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| package ch.dissem.apps.abit.synchronization; | ||||
|  | ||||
| import android.accounts.Account; | ||||
| import android.content.AbstractThreadedSyncAdapter; | ||||
| import android.content.ContentProviderClient; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.SyncResult; | ||||
| import android.os.Bundle; | ||||
| 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.service.Singleton; | ||||
| import ch.dissem.bitmessage.BitmessageContext; | ||||
|  | ||||
| /** | ||||
|  * Sync Adapter to synchronize with the Bitmessage network - fetches | ||||
|  * new objects and then disconnects. | ||||
|  */ | ||||
| public class SyncAdapter extends AbstractThreadedSyncAdapter { | ||||
|     private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class); | ||||
|  | ||||
|     public static final String AUTHORITY = "ch.dissem.bitmessage.provider"; | ||||
|  | ||||
|     private final BitmessageContext bmc; | ||||
|  | ||||
|     public SyncAdapter(Context context) { | ||||
|         super(context, true, false); | ||||
|         bmc = Singleton.getBitmessageContext(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { | ||||
|         // If the Bitmessage context acts as a full node, synchronization isn't necessary | ||||
|         if (bmc.isRunning()) return; | ||||
|  | ||||
|         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);// FIXME | ||||
|             } catch (NumberFormatException e) { | ||||
|                 LOG.error("Invalid port " + portString); | ||||
|                 // TODO: show error as notification | ||||
|                 return; | ||||
|             } | ||||
|         } else { | ||||
|             port = 8444; | ||||
|         } | ||||
|         long timeoutInSeconds = preferences.getInt("sync_timeout", 120); | ||||
|         try { | ||||
|             LOG.info("Synchronization started"); | ||||
|             bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true); | ||||
|             LOG.info("Synchronization finished"); | ||||
|         } catch (UnknownHostException e) { | ||||
|             LOG.error("Couldn't synchronize", e); | ||||
|             // TODO: show error as notification | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,49 @@ | ||||
| package ch.dissem.apps.abit.synchronization; | ||||
|  | ||||
| import android.app.Service; | ||||
| import android.content.Intent; | ||||
| import android.os.IBinder; | ||||
|  | ||||
| /** | ||||
|  * Define a Service that returns an IBinder for the | ||||
|  * sync adapter class, allowing the sync adapter framework to call | ||||
|  * onPerformSync(). | ||||
|  */ | ||||
| public class SyncService extends Service { | ||||
|     // Storage for an instance of the sync adapter | ||||
|     private static SyncAdapter syncAdapter = null; | ||||
|     // Object to use as a thread-safe lock | ||||
|     private static final Object syncAdapterLock = new Object(); | ||||
|  | ||||
|     /* | ||||
|      * Instantiate the sync adapter object. | ||||
|      */ | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         /* | ||||
|          * Create the sync adapter as a singleton. | ||||
|          * Set the sync adapter as syncable | ||||
|          * Disallow parallel syncs | ||||
|          */ | ||||
|         synchronized (syncAdapterLock) { | ||||
|             if (syncAdapter == null) { | ||||
|                 syncAdapter = new SyncAdapter(getApplicationContext()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Return an object that allows the system to invoke | ||||
|      * the sync adapter. | ||||
|      */ | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         /* | ||||
|          * Get the object that allows external processes | ||||
|          * to call onPerformSync(). The object is created | ||||
|          * in the base class code when the SyncAdapter | ||||
|          * constructors call super() | ||||
|          */ | ||||
|         return syncAdapter.getSyncAdapterBinder(); | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| <?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" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:padding="16dp"> | ||||
| @@ -22,8 +21,8 @@ | ||||
|             android:id="@+id/label" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|                 android:inputType="textPersonName" | ||||
|                 android:hint="@string/label"/> | ||||
|             android:hint="@string/label" | ||||
|             android:inputType="textPersonName" /> | ||||
|  | ||||
|     </android.support.design.widget.TextInputLayout> | ||||
|  | ||||
| @@ -31,53 +30,52 @@ | ||||
|         android:id="@+id/import_contact" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|             android:text="@string/import_contact" | ||||
|         android:layout_below="@+id/label_wrapper" | ||||
|         android:layout_centerHorizontal="true" | ||||
|             android:layout_marginTop="8dp"/> | ||||
|         android:layout_marginTop="8dp" | ||||
|         android:text="@string/import_contact" /> | ||||
|  | ||||
|     <Switch | ||||
|         android:id="@+id/subscribe" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|             android:text="@string/subscribe" | ||||
|         android:layout_below="@+id/import_contact" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:layout_marginTop="8dp" | ||||
|             android:layout_marginBottom="8dp"/> | ||||
|  | ||||
|     <Button | ||||
|             android:id="@+id/compose_message" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="Message" | ||||
|             style="?android:attr/borderlessButtonStyle" | ||||
|             android:layout_below="@+id/subscribe" | ||||
|             android:layout_alignParentRight="true" | ||||
|             android:layout_alignParentEnd="true" | ||||
|             android:layout_marginTop="12dp" | ||||
|             android:layout_marginBottom="12dp"/> | ||||
|         android:text="@string/subscribe" /> | ||||
|  | ||||
|     <Button | ||||
|         android:id="@+id/do_import" | ||||
|         style="?android:attr/borderlessButtonStyle" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|             android:text="@string/do_import" | ||||
|             style="?android:attr/borderlessButtonStyle" | ||||
|             android:layout_below="@+id/subscribe" | ||||
|             android:layout_alignParentRight="true" | ||||
|         android:layout_alignParentEnd="true" | ||||
|         android:layout_alignParentRight="true" | ||||
|         android:layout_below="@+id/subscribe" | ||||
|         android:layout_marginBottom="12dp" | ||||
|         android:layout_marginTop="12dp" | ||||
|             android:layout_marginBottom="12dp"/> | ||||
|         android:text="@string/do_import" /> | ||||
|  | ||||
|     <Button | ||||
|         android:id="@+id/compose_message" | ||||
|         style="?android:attr/borderlessButtonStyle" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_alignTop="@+id/do_import" | ||||
|         android:layout_below="@+id/subscribe" | ||||
|         android:layout_toLeftOf="@+id/do_import" | ||||
|         android:layout_toStartOf="@+id/do_import" | ||||
|         android:text="@string/write_message" /> | ||||
|  | ||||
|     <Button | ||||
|         android:id="@+id/cancel" | ||||
|         style="?android:attr/borderlessButtonStyle" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|             android:text="@string/cancel" | ||||
|             style="?android:attr/borderlessButtonStyle" | ||||
|             android:layout_alignTop="@+id/do_import" | ||||
|             android:layout_toLeftOf="@+id/do_import" | ||||
|             android:layout_toStartOf="@+id/do_import"/> | ||||
|         android:layout_alignTop="@+id/compose_message" | ||||
|         android:layout_toLeftOf="@+id/compose_message" | ||||
|         android:layout_toStartOf="@+id/compose_message" | ||||
|         android:text="@string/cancel" /> | ||||
|  | ||||
| </RelativeLayout> | ||||
|   | ||||
| @@ -33,4 +33,8 @@ | ||||
|     <string name="stream_number">Stream #%d</string> | ||||
|     <string name="enabled">Aktiv</string> | ||||
|     <string name="title_subscription_detail">Abonnement</string> | ||||
|     <string name="sync_timeout">Zeitbeschränkung der Synchronisierung</string> | ||||
|     <string name="sync_timeout_summary">Timeout in Sekunden</string> | ||||
|     <string name="trusted_node">Vertrauenswürdiger Knoten</string> | ||||
|     <string name="trusted_node_summary">Diese Adresse wird für die Synchronisation verwendet</string> | ||||
| </resources> | ||||
| @@ -33,4 +33,9 @@ | ||||
|     <string name="empty_trash">Empty Trash</string> | ||||
|     <string name="stream_number">Stream #%d</string> | ||||
|     <string name="enabled">Enabled</string> | ||||
|     <string name="trusted_node">Trusted node</string> | ||||
|     <string name="trusted_node_summary">Use this node for synchronization</string> | ||||
|     <string name="sync_timeout">Synchronization Timeout</string> | ||||
|     <string name="sync_timeout_summary">Timeout in seconds</string> | ||||
|     <string name="write_message">Write message</string> | ||||
| </resources> | ||||
|   | ||||
							
								
								
									
										6
									
								
								app/src/main/res/xml/authenticator.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/src/main/res/xml/authenticator.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:accountType="bitmessage.dissem.ch" | ||||
|     android:icon="@mipmap/ic_launcher" | ||||
|     android:label="@string/app_name" | ||||
|     android:smallIcon="@mipmap/ic_launcher" /> | ||||
| @@ -9,8 +9,19 @@ | ||||
|             android:entryValues="@array/connection_mode_values"/> | ||||
|     --> | ||||
|     <SwitchPreference | ||||
|         android:defaultValue="true" | ||||
|         android:key="wifi_only" | ||||
|             android:title="@string/wifi_only" | ||||
|         android:summary="@string/wifi_only_summary" | ||||
|             android:defaultValue="true"/> | ||||
|         android:title="@string/wifi_only" /> | ||||
|     <EditTextPreference | ||||
|         android:inputType="textUri" | ||||
|         android:key="trusted_node" | ||||
|         android:summary="@string/trusted_node_summary" | ||||
|         android:title="@string/trusted_node" /> | ||||
|     <EditTextPreference | ||||
|         android:defaultValue="120" | ||||
|         android:inputType="number" | ||||
|         android:key="sync_timeout" | ||||
|         android:summary="@string/sync_timeout_summary" | ||||
|         android:title="@string/sync_timeout" /> | ||||
| </PreferenceScreen> | ||||
							
								
								
									
										9
									
								
								app/src/main/res/xml/syncadapter.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/xml/syncadapter.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <sync-adapter | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:contentAuthority="ch.dissem.bitmessage.provider" | ||||
|     android:accountType="ch.dissem.bitmessage" | ||||
|     android:userVisible="true" | ||||
|     android:supportsUploading="true" | ||||
|     android:allowParallelSyncs="false" | ||||
|     android:isAlwaysSyncable="true"/> | ||||
		Reference in New Issue
	
	Block a user