{"id":1037,"date":"2013-07-09T01:00:00","date_gmt":"2013-07-08T23:00:00","guid":{"rendered":"https:\/\/www.fussylogic.co.uk\/blog\/?p=1037"},"modified":"2014-10-10T12:08:30","modified_gmt":"2014-10-10T11:08:30","slug":"android-content-providers-and-synchronisation","status":"publish","type":"post","link":"https:\/\/www.fussylogic.co.uk\/blog\/?p=1037","title":{"rendered":"Android Content Providers and Synchronisation"},"content":{"rendered":"<p><a href=\"?p=1031\">Last<\/a> <a href=\"?p=1035\">time<\/a> we looked at creating accounts for our own custom purposes on an Android device. Now we\u00e2\u20ac\u2122ve got an account, we\u00e2\u20ac\u2122d like to do something with it. This series of articles will cover using an account to regularly synchronise some state on an upstream server with state held on the Android device.<\/p>\n<p>Implementing synchronisation opens up a new can of worms for us. Synchronisation is done in tandem with Android\u00e2\u20ac\u2122s <a href=\"http:\/\/developer.android.com\/guide\/topics\/providers\/content-providers.html\"><code>ContentProvider<\/code><\/a> subsystem. We\u00e2\u20ac\u2122ll have to implement one first before we can start on a synchroniser.<\/p>\n<p>Each content provider is registered with the system under a unique name. We tell the system about our provider by adding it to the manifest.<\/p>\n<pre class=\"sourceCode xml\"><code class=\"sourceCode xml\">    <span class=\"kw\">&lt;provider<\/span><span class=\"ot\"> android:name=<\/span><span class=\"st\">&quot;.Provider&quot;<\/span>\n<span class=\"ot\">        android:exported=<\/span><span class=\"st\">&quot;false&quot;<\/span>\n<span class=\"ot\">        android:authorities=<\/span><span class=\"st\">&quot;@string\/provider_authority&quot;<\/span><span class=\"kw\">&gt;<\/span>\n    <span class=\"kw\">&lt;\/provider&gt;<\/span><\/code><\/pre>\n<p>As usual we\u00e2\u20ac\u2122ll need this unique name in our code as well, so we\u00e2\u20ac\u2122ll use a string reference. I\u00e2\u20ac\u2122ve marked it as non-exported here as I don\u00e2\u20ac\u2122t want to offer the data stored in it outside this app.<\/p>\n<p>A real-world provider implementation will most likely use Android\u00e2\u20ac\u2122s built in SQLite. I\u00e2\u20ac\u2122d like to keep things uncomplicated here and avoid having to describe databases as well. Instead I\u00e2\u20ac\u2122m going to make as simple a provider as I can, and store a single field, say a street address. However, in being simple we\u00e2\u20ac\u2122ll see a facility that\u00e2\u20ac\u2122s hard to find described \u00e2\u20ac\u201c non-table-based providers.<\/p>\n<p>Let\u00e2\u20ac\u2122s begin then. A content provider uses URIs as the means of communicating the particular piece of data wanted. They are always of this form:<\/p>\n<pre><code>content:\/\/PROVIDER_AUTHORITY\/per_provider_path<\/code><\/pre>\n<p>All providers use the \u00e2\u20ac\u0153<code>content:\/\/<\/code>\u00e2\u20ac\u009d schema. <code>PROVIDER_AUTHORITY<\/code> is the unique string we\u00e2\u20ac\u2122ve already seen, and is what Android uses to direct provider requests on the client side to the appropriate implementation on the server side. The path is, for the most part, the provider\u00e2\u20ac\u2122s choice.<\/p>\n<p>All content providers derive from the <code>ContentProvider<\/code> class. Most of the URI decoding work is done by a convenience class from Android, <code>UriMatcher<\/code>.<\/p>\n<pre class=\"sourceCode java\"><code class=\"sourceCode java\"><span class=\"kw\">public<\/span> <span class=\"kw\">class<\/span> Provider <span class=\"kw\">extends<\/span> ContentProvider {\n    <span class=\"kw\">public<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> String ADDRESS_FILENAME = <span class=\"st\">&quot;address.json&quot;<\/span>\n    <span class=\"kw\">public<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> String PROVIDER_AUTHORITY = <span class=\"st\">&quot;uk.co.fussylogic.exampleprovider&quot;<\/span>;\n    <span class=\"kw\">public<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> Uri PROVIDER_BASE_URI =\n        Uri.<span class=\"fu\">parse<\/span>(ContentResolver.<span class=\"fu\">SCHEME_CONTENT<\/span> + <span class=\"st\">&quot;:\/\/&quot;<\/span> + PROVIDER_AUTHORITY);\n    <span class=\"kw\">public<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> Uri ADDRESS_URI = Uri.<span class=\"fu\">withAppendedPath<\/span>(PROVIDER_URI, <span class=\"st\">&quot;address&quot;<\/span>);\n\n    <span class=\"co\">\/\/ UriMatcher codes<\/span>\n    <span class=\"kw\">private<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> <span class=\"dt\">int<\/span> URIMATCH_ID_ADDRESS = <span class=\"dv\">1<\/span>;\n\n    <span class=\"kw\">private<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> UriMatcher sURIMatcher = <span class=\"kw\">new<\/span> <span class=\"fu\">UriMatcher<\/span>(UriMatcher.<span class=\"fu\">NO_MATCH<\/span>);\n    <span class=\"dt\">static<\/span> {\n        sURIMatcher.<span class=\"fu\">addURI<\/span>(Constants.<span class=\"fu\">PROVIDER_AUTHORITY<\/span>, <span class=\"st\">&quot;address&quot;<\/span>, URIMATCH_ID_ADDRESS);\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> String <span class=\"fu\">getType<\/span>(Uri uri) {\n        <span class=\"co\">\/\/ Pattern match the URI<\/span>\n        <span class=\"dt\">int<\/span> uriType = sURIMatcher.<span class=\"fu\">match<\/span>(uri);\n\n        <span class=\"kw\">switch<\/span> (uriType) {\n        <span class=\"kw\">case<\/span> URIMATCH_ID_ADDRESS:\n            <span class=\"kw\">return<\/span> <span class=\"st\">&quot;application\/json&quot;<\/span>;\n        <span class=\"kw\">default<\/span>:\n            <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Unknown URI: &quot;<\/span> + uri);        \n        }\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> Cursor <span class=\"fu\">query<\/span>(Uri uri, String[] projection, String selection,\n            String[] selectionArgs, String sortOrder) {\n        <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Unknown URI: &quot;<\/span> + uri);\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> Uri <span class=\"fu\">insert<\/span>(Uri uri, ContentValues values) {\n        <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Unknown\/unqueryable URI: &quot;<\/span> + uri);\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> <span class=\"dt\">int<\/span> <span class=\"fu\">delete<\/span>(Uri uri, String selection, String[] selectionArgs) {\n        <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Unknown URI: &quot;<\/span> + uri);\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> <span class=\"dt\">int<\/span> <span class=\"fu\">update<\/span>(Uri uri, ContentValues values, String selection,\n            String[] selectionArgs) {\n        <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Unknown URI: &quot;<\/span> + uri);\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> ParcelFileDescriptor <span class=\"fu\">openFile<\/span>(Uri uri, String mode)\n            <span class=\"kw\">throws<\/span> FileNotFoundException {\n        File fileName;\n        <span class=\"dt\">int<\/span> uriType = sURIMatcher.<span class=\"fu\">match<\/span>(uri);\n        <span class=\"kw\">switch<\/span> (uriType) {\n        <span class=\"kw\">case<\/span> URIMATCH_ID_ADDRESS:\n            fileName = <span class=\"kw\">new<\/span> File(<span class=\"fu\">getContext<\/span>().<span class=\"fu\">getFilesDir<\/span>(), ADDRESS_FILENAME);\n            <span class=\"kw\">break<\/span>;\n        <span class=\"kw\">default<\/span>:\n            <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IllegalArgumentException(<span class=\"st\">&quot;Non-file-based URI: &quot;<\/span> + uri);\n        }\n\n        <span class=\"dt\">int<\/span> imode = <span class=\"dv\">0<\/span>;\n        <span class=\"kw\">if<\/span> (mode.<span class=\"fu\">contains<\/span>(<span class=\"st\">&quot;w&quot;<\/span>)) imode |= ParcelFileDescriptor.<span class=\"fu\">MODE_WRITE_ONLY<\/span>\n                | ParcelFileDescriptor.<span class=\"fu\">MODE_CREATE<\/span>;\n        <span class=\"kw\">if<\/span> (mode.<span class=\"fu\">contains<\/span>(<span class=\"st\">&quot;r&quot;<\/span>)) imode |= ParcelFileDescriptor.<span class=\"fu\">MODE_READ_ONLY<\/span>;\n        <span class=\"kw\">if<\/span> (mode.<span class=\"fu\">contains<\/span>(<span class=\"st\">&quot;+&quot;<\/span>)) imode |= ParcelFileDescriptor.<span class=\"fu\">MODE_APPEND<\/span>;\n\n        <span class=\"kw\">if<\/span>( (imode &amp; ParcelFileDescriptor.<span class=\"fu\">MODE_READ_ONLY<\/span>) != <span class=\"dv\">0<\/span> &amp;&amp; !fileName.<span class=\"fu\">exists<\/span>()) {\n            Log.<span class=\"fu\">d<\/span>(TAG, fileName + <span class=\"st\">&quot; doesn&#39;t exist (&quot;<\/span> + imode + <span class=\"st\">&quot;)&quot;<\/span>);\n        }\n\n        <span class=\"kw\">return<\/span> ParcelFileDescriptor.<span class=\"fu\">open<\/span>(fileName, imode);\n    }\n}<\/code><\/pre>\n<p>Normally <code>ContentProvider<\/code> child classes wouldn\u00e2\u20ac\u2122t be so bare, and they certainly wouldn\u00e2\u20ac\u2122t leave all the key methods, <code>query()<\/code>, <code>insert()<\/code>, <code>delete()<\/code>, and <code>update()<\/code>, throwing exceptions. We\u00e2\u20ac\u2122re also only supporting a single URI so using <code>UriMatcher<\/code> is overkill.<\/p>\n<p>This should be enough to let us read and write a file under the <code>\/address<\/code> content URI; which is, in turn, enough to let us write a synchronisation service.<\/p>\n<p>As with our provider, we inform Android about the synchronisation service in the manifest. It\u00e2\u20ac\u2122s very similar to how we added an authenticator:<\/p>\n<pre class=\"sourceCode xml\"><code class=\"sourceCode xml\">    <span class=\"kw\">&lt;service<\/span><span class=\"ot\"> android:name=<\/span><span class=\"st\">&quot;.SyncService&quot;<\/span>\n<span class=\"ot\">        android:exported=<\/span><span class=\"st\">&quot;false&quot;<\/span>\n<span class=\"ot\">        android:label=<\/span><span class=\"st\">&quot;@string\/app_name&quot;<\/span> <span class=\"kw\">&gt;<\/span>\n        <span class=\"kw\">&lt;intent-filter&gt;<\/span>\n            <span class=\"kw\">&lt;action<\/span><span class=\"ot\"> android:name=<\/span><span class=\"st\">&quot;android.content.SyncAdapter&quot;<\/span> <span class=\"kw\">\/&gt;<\/span>\n        <span class=\"kw\">&lt;\/intent-filter&gt;<\/span>\n\n        <span class=\"kw\">&lt;meta-data<\/span>\n<span class=\"ot\">            android:name=<\/span><span class=\"st\">&quot;android.content.SyncAdapter&quot;<\/span>\n<span class=\"ot\">            android:resource=<\/span><span class=\"st\">&quot;@xml\/syncadapter&quot;<\/span> <span class=\"kw\">\/&gt;<\/span>\n    <span class=\"kw\">&lt;\/service&gt;<\/span><\/code><\/pre>\n<p>The presence of the <code>SyncAdapter<\/code> intent filter makes Android go looking for the <code>android.content.SyncAdapter<\/code> meta-data, which tells which XML file to find the synchroniser information.<\/p>\n<pre class=\"sourceCode xml\"><code class=\"sourceCode xml\"><span class=\"kw\">&lt;?xml<\/span> version=&quot;1.0&quot; encoding=&quot;utf-8&quot;<span class=\"kw\">?&gt;<\/span>\n<span class=\"kw\">&lt;sync-adapter<\/span><span class=\"ot\"> xmlns:android=<\/span><span class=\"st\">&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;<\/span>\n<span class=\"ot\">    android:contentAuthority=<\/span><span class=\"st\">&quot;@string\/provider_authority&quot;<\/span>\n<span class=\"ot\">    android:accountType=<\/span><span class=\"st\">&quot;@string\/authenticator_account_type&quot;<\/span>\n<span class=\"ot\">    android:userVisible=<\/span><span class=\"st\">&quot;true&quot;<\/span>\n<span class=\"ot\">    android:supportsUploading=<\/span><span class=\"st\">&quot;true&quot;<\/span>\n<span class=\"ot\">    android:allowParallelSyncs=<\/span><span class=\"st\">&quot;false&quot;<\/span>\n<span class=\"ot\">    android:isAlwaysSyncable=<\/span><span class=\"st\">&quot;true&quot;<\/span><span class=\"kw\">\/&gt;<\/span><\/code><\/pre>\n<p>We\u00e2\u20ac\u2122ve already seen the setting for <code>contentAuthority<\/code> above, and <code>accountType<\/code> is the account we\u00e2\u20ac\u2122re syncing for \u00e2\u20ac\u201c this should match what we set in authenticator definition. The other fields are self-explanatory or easily looked up in the documents.<\/p>\n<p>The definition of our service goes along very similar lines to the authentication service.<\/p>\n<pre class=\"sourceCode java\"><code class=\"sourceCode java\"><span class=\"kw\">public<\/span> <span class=\"kw\">class<\/span> SyncService <span class=\"kw\">extends<\/span> Service {\n    <span class=\"co\">\/\/ We keep one sync adapter for the service<\/span>\n    <span class=\"kw\">private<\/span> <span class=\"dt\">static<\/span> <span class=\"dt\">final<\/span> Object sSyncAdapterLock = <span class=\"kw\">new<\/span> Object();\n    <span class=\"kw\">private<\/span> <span class=\"dt\">static<\/span> SyncAdapter sSyncAdapter = <span class=\"kw\">null<\/span>;\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> <span class=\"dt\">void<\/span> <span class=\"fu\">onCreate<\/span>() {\n        <span class=\"co\">\/\/ The race condition we have to worry about is if multiple<\/span>\n        <span class=\"co\">\/\/ SyncService&#39;s get created at the same time, and we create multiple<\/span>\n        <span class=\"co\">\/\/ SyncAdapters.<\/span>\n        <span class=\"kw\">synchronized<\/span> (sSyncAdapterLock) {\n            <span class=\"kw\">if<\/span> (sSyncAdapter == <span class=\"kw\">null<\/span>) {\n                sSyncAdapter = <span class=\"kw\">new<\/span> <span class=\"fu\">SyncAdapter<\/span>(<span class=\"fu\">getApplicationContext<\/span>(), <span class=\"kw\">true<\/span>);\n            }\n        }\n    }\n\n    <span class=\"fu\">@Override<\/span>\n    <span class=\"kw\">public<\/span> IBinder <span class=\"fu\">onBind<\/span>(Intent intent) {\n        <span class=\"co\">\/\/ What if onCreate() from the first thread is still running, but<\/span>\n        <span class=\"co\">\/\/ hasn&#39;t created the SyncAdapter yet?  We should be locked here<\/span>\n        <span class=\"co\">\/\/ too.<\/span>\n        <span class=\"kw\">synchronized<\/span> (sSyncAdapterLock) {\n            <span class=\"kw\">return<\/span> sSyncAdapter.<span class=\"fu\">getSyncAdapterBinder<\/span>();\n        }\n    }\n\n    <span class=\"co\">\/\/ -------------------------------<\/span>\n\n    <span class=\"kw\">public<\/span> <span class=\"kw\">class<\/span> SyncAdapter <span class=\"kw\">extends<\/span> AbstractThreadedSyncAdapter {\n        <span class=\"kw\">protected<\/span> <span class=\"dt\">final<\/span> AccountManager mAccountManager;\n\n        <span class=\"kw\">public<\/span> <span class=\"fu\">SyncAdapter<\/span>(Context context, <span class=\"dt\">boolean<\/span> autoInitialize) {\n            <span class=\"kw\">super<\/span>(context, autoInitialize);\n            mContext = context;\n            mAccountManager = AccountManager.<span class=\"fu\">get<\/span>(context);\n        }\n\n        <span class=\"fu\">@Override<\/span>\n        <span class=\"kw\">public<\/span> <span class=\"dt\">void<\/span> <span class=\"fu\">onPerformSync<\/span>(Account account, Bundle extras,\n                String authority, ContentProviderClient provider,\n                SyncResult syncResult) {\n            <span class=\"kw\">try<\/span> {\n                <span class=\"fu\">performSync<\/span>(account, extras, authority, provider, syncResult);\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> AuthenticatorException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n                syncResult.<span class=\"fu\">stats<\/span>.<span class=\"fu\">numParseExceptions<\/span>++;\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> OperationCanceledException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> IOException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n                syncResult.<span class=\"fu\">stats<\/span>.<span class=\"fu\">numIoExceptions<\/span>++;\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> AuthenticationException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n                syncResult.<span class=\"fu\">stats<\/span>.<span class=\"fu\">numAuthExceptions<\/span>++;\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> JSONException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n                syncResult.<span class=\"fu\">stats<\/span>.<span class=\"fu\">numParseExceptions<\/span>++;\n            } <span class=\"kw\">catch<\/span> (<span class=\"dt\">final<\/span> RuntimeException e) {\n                Log.<span class=\"fu\">e<\/span>(TAG, <span class=\"st\">&quot;Synchronise failed &quot;<\/span>, e);\n                <span class=\"co\">\/\/ Treat runtime exception as an I\/O error<\/span>\n                syncResult.<span class=\"fu\">stats<\/span>.<span class=\"fu\">numIoExceptions<\/span>++;\n            }\n        }\n\n        <span class=\"kw\">private<\/span> <span class=\"dt\">void<\/span> <span class=\"fu\">performSync<\/span>(Account account, Bundle extras,\n                String authority, ContentProviderClient provider,\n                SyncResult syncResult) <span class=\"kw\">throws<\/span> AuthenticatorException,\n                OperationCanceledException, IOException,\n                AuthenticationException, ParseException, JSONException {\n\n            <span class=\"co\">\/\/ Use the account manager to request the AuthToken we&#39;ll need<\/span>\n            <span class=\"co\">\/\/ to talk to our sample server.  If we don&#39;t have an AuthToken<\/span>\n            <span class=\"co\">\/\/ yet, this could involve a round-trip to the server to request<\/span>\n            <span class=\"co\">\/\/ an AuthToken.  We can block here as we&#39;re already in a non-UI<\/span>\n            <span class=\"co\">\/\/ thread.<\/span>\n            <span class=\"dt\">final<\/span> String authToken = mAccountManager.<span class=\"fu\">blockingGetAuthToken<\/span>(\n                    account,\n                    <span class=\"fu\">getString<\/span>(R.<span class=\"fu\">string<\/span>.<span class=\"fu\">auth_token_type_default<\/span>),\n                    NOTIFY_AUTH_FAILURE);\n            <span class=\"kw\">if<\/span>( authToken == <span class=\"kw\">null<\/span> )\n                <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> AuthenticationException(<span class=\"st\">&quot;No authentication token available for account, &quot;<\/span>\n                        + account );\n\n            <span class=\"co\">\/\/ --- Fetch from remote<\/span>\n            <span class=\"co\">\/\/ NetworkCommunicator is application-specific, you would<\/span>\n            <span class=\"co\">\/\/ implement it as you see fit<\/span>\n            NetworkCommunicator nc = <span class=\"kw\">new<\/span> <span class=\"fu\">NetworkCommunicator<\/span>();\n            nc.<span class=\"fu\">setAuthToken<\/span>(token);\n            JSONObject json = nc.<span class=\"fu\">fetchAddress<\/span>();\n\n            <span class=\"co\">\/\/ --- Write to local<\/span>\n            <span class=\"co\">\/\/ Get the appropriate URI as a constant from our provider<\/span>\n            Uri uri = Provider.<span class=\"fu\">ADDRESS_URI<\/span>;\n            OutputStream out = <span class=\"kw\">null<\/span>;\n            <span class=\"kw\">try<\/span> {\n                out = provider.<span class=\"fu\">openOutputStream<\/span>(uri, <span class=\"st\">&quot;w&quot;<\/span>);\n            } <span class=\"kw\">catch<\/span> (FileNotFoundException e) {\n                Log.<span class=\"fu\">w<\/span>(TAG, <span class=\"st\">&quot;Content URI not available for writing, &quot;<\/span> + uri);\n                <span class=\"kw\">throw<\/span> <span class=\"kw\">new<\/span> IOException(e);\n            }\n            out.<span class=\"fu\">write<\/span>(json.<span class=\"fu\">toString<\/span>().<span class=\"fu\">getBytes<\/span>(Charset.<span class=\"fu\">forName<\/span>(<span class=\"st\">&quot;UTF-8&quot;<\/span>)));\n            out.<span class=\"fu\">flush<\/span>();\n            out.<span class=\"fu\">close<\/span>();\n        }        \n    }\n}<\/code><\/pre>\n<p>This function is where we pull all the parts together. We fetch an authentication token from the account manager; we fetch from the remote service using that token; then write whatever we\u00e2\u20ac\u2122ve fetched to the provider.<\/p>\n<p>The actual mechanics of your synchronisation are obviously going to be application specific; and probably considerably more involved than this.<\/p>\n<p>Let\u00e2\u20ac\u2122s take a moment to notice what this tells us about Android. We have accounts (with a type that ties them to our authenticator), and within that account we can store multiple tokens, themselves each of a named type (again, as suits our application). Then we have content providers. A content provider is not (directly) tied to an account, it exists on its own. We tie a provider and an account (or rather an account type) together via a synchroniser. We could easily have multiple providers all holding different information from a single account. Each of those providers is tied to the account with its own synchronisation service. Go to your Android settings page and look in the accounts section. Open up the \u00e2\u20ac\u0153Google\u00e2\u20ac\u009d accounts, and pick one account. You are shown the \u00e2\u20ac\u0153sync\u00e2\u20ac\u009d page, mine has the following potential syncs:<\/p>\n<ul>\n<li>App Data<\/li>\n<li>Calendar<\/li>\n<li>Chrome<\/li>\n<li>Contacts<\/li>\n<li>Drive<\/li>\n<li>Gmail<\/li>\n<li>Google Currents<\/li>\n<li>Google Photos<\/li>\n<li>Google Play Books<\/li>\n<li>Google Play Magazines<\/li>\n<li>Google Play Movies<\/li>\n<li>Google Play Music<\/li>\n<li>Google+<\/li>\n<li>Google+ Auto-backup<\/li>\n<li>Keep<\/li>\n<li>My Tracks<\/li>\n<li>People details<\/li>\n<\/ul>\n<p>Each of these is provided by a different synchronisation service, potentially in different apps. Each of the synchronisation services is updating a different content provider, or part of a content provider. Nothing stops you from using a single account for multiple apps and content stores just like Google. You can find all the providers on your device like this:<\/p>\n<pre><code>$ adb shell dumpsys | grep ContentProviderRecord<\/code><\/pre>\n<p>The output of <code>dumpsys<\/code> can also tell you all the synchronisers attached to a particular provider for each of these <code>ContentProviderRecord<\/code>s.<\/p>\n<pre><code>$ adb shell dumpsys | grep SyncAdapterType<\/code><\/pre>\n<p>In fact, the whole output of <code>dumpsys<\/code> is highly educational once you\u00e2\u20ac\u2122ve got a grasp of how Android operates.<\/p>\n<p>This relationship between synchroniser and provider is of particular relevance when we look at the interface through which we configure our synchronisation service. The interface is via the provider itself. For example, after we create an account, we might wish to configure its sync settings. We do so like this:<\/p>\n<pre class=\"sourceCode java\"><code class=\"sourceCode java\"><span class=\"kw\">public<\/span> <span class=\"fu\">configureSync<\/span>(Account account) {\n    <span class=\"co\">\/\/ Which provider&#39;s synchroniser are we configuring?  We&#39;ve hard<\/span>\n    <span class=\"co\">\/\/ coded one<\/span>\n    String providerAuthority =  <span class=\"fu\">getApplicationContext<\/span>().<span class=\"fu\">getString<\/span>(R.<span class=\"fu\">string<\/span>.<span class=\"fu\">provider_authority<\/span>);\n    <span class=\"co\">\/\/ Is it possible to sync?<\/span>\n    ContentResolver.<span class=\"fu\">setIsSyncable<\/span>(account, providerAuthority, <span class=\"kw\">true<\/span>);\n    <span class=\"co\">\/\/ Should sync be done automatically by Android when the provider<\/span>\n    <span class=\"co\">\/\/ sends a notifyChange() with syncToNetwork set to true?<\/span>\n    ContentResolver.<span class=\"fu\">setSyncAutomatically<\/span>(account, providerAuthority, <span class=\"kw\">true<\/span>);\n    <span class=\"co\">\/\/ Set some sync parameters<\/span>\n    Bundle params = <span class=\"kw\">new<\/span> <span class=\"fu\">Bundle<\/span>();\n    params.<span class=\"fu\">putBoolean<\/span>(ContentResolver.<span class=\"fu\">SYNC_EXTRAS_EXPEDITED<\/span>, <span class=\"kw\">false<\/span>);\n    params.<span class=\"fu\">putBoolean<\/span>(ContentResolver.<span class=\"fu\">SYNC_EXTRAS_DO_NOT_RETRY<\/span>, <span class=\"kw\">false<\/span>);\n    params.<span class=\"fu\">putBoolean<\/span>(ContentResolver.<span class=\"fu\">SYNC_EXTRAS_MANUAL<\/span>, <span class=\"kw\">false<\/span>);\n    <span class=\"co\">\/\/ How often should automatic sync be done? (say 15 minutes)<\/span>\n    ContentResolver.<span class=\"fu\">addPeriodicSync<\/span>(account, syncAuthority, params, <span class=\"dv\">15<\/span>*<span class=\"dv\">60<\/span>);\n    <span class=\"co\">\/\/ Request a sync right now<\/span>\n    ContentResolver.<span class=\"fu\">requestSync<\/span>(account, syncAuthority, params);\n}<\/code><\/pre>\n<p>Next time we\u00e2\u20ac\u2122ll look at a more capable provider, and supplying storage for that provider with SQLite.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last time we looked at creating accounts for our own custom purposes on an Android device. Now we\u00e2\u20ac\u2122ve got an account, we\u00e2\u20ac\u2122d like to do something with it. This series of articles will cover using an account to regularly synchronise some state on an upstream server with state held on the Android device. Implementing synchronisation\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.fussylogic.co.uk\/blog\/?p=1037\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[9,69,42,71,6],"_links":{"self":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1037"}],"collection":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1037"}],"version-history":[{"count":6,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1037\/revisions"}],"predecessor-version":[{"id":1281,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1037\/revisions\/1281"}],"wp:attachment":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1037"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1037"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1037"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}