Create a gist now

Instantly share code, notes, and snippets.

Sync Adapter example
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.packagename">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<application
android:allowBackup="false"
android:label="@string/app_name"
android:supportsRtl="true">
<service android:name=".AuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<provider
android:name=".StubContentProvider"
android:authorities="@string/authority"
android:exported="false"
android:syncable="true" />
<service
android:name=".ConfigurationSyncAdapterService"
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>
public class Authenticator extends AbstractAccountAuthenticator {
public Authenticator(final Context context) {
super(context);
}
/**
* Returns a Bundle that contains the Intent of the activity that can be used to edit the
* properties. In order to indicate success the activity should call response.setResult() with a
* non-null Bundle.
*
* @param response used to set the result for the request. If the Constants.INTENT_KEY is set
* in the bundle then this response field is to be used for sending future
* results if and when the Intent is started.
* @param accountType the AccountType whose properties are to be edited.
* @return a Bundle containing the result or the Intent to start to continue the request. If
* this is null then the request is considered to still be active and the result should sent
* later using response.
*/
@Override
public Bundle editProperties(final AccountAuthenticatorResponse response,
final String accountType) {
throw new UnsupportedOperationException();
}
/**
* Adds an account of the specified accountType.
*
* @param response to send the result back to the AccountManager, will never be null
* @param accountType the type of account to add, will never be null
* @param authTokenType the type of auth token to retrieve after adding the account, may be
* null
* @param requiredFeatures a String array of authenticator-specific features that the added
* account must support, may be null
* @param options a Bundle of authenticator-specific options, may be null
* @return a Bundle result or null if the result is to be returned via the response. The result
* will contain either: <ul> <li> {@link AccountManager#KEY_INTENT}, or <li> {@link
* AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the account
* that was added, or <li> {@link AccountManager#KEY_ERROR_CODE} and {@link
* AccountManager#KEY_ERROR_MESSAGE} to indicate an error </ul>
* @throws NetworkErrorException if the authenticator could not honor the request due to a
* network error
*/
@Override
public Bundle addAccount(final AccountAuthenticatorResponse response, final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle options)
throws NetworkErrorException {
return null;
}
/**
* Checks that the user knows the credentials of an account.
*
* @param response to send the result back to the AccountManager, will never be null
* @param account the account whose credentials are to be checked, will never be null
* @param options a Bundle of authenticator-specific options, may be null
* @return a Bundle result or null if the result is to be returned via the response. The result
* will contain either: <ul> <li> {@link AccountManager#KEY_INTENT}, or <li> {@link
* AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise <li> {@link
* AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an
* error </ul>
* @throws NetworkErrorException if the authenticator could not honor the request due to a
* network error
*/
@Override
public Bundle confirmCredentials(final AccountAuthenticatorResponse response,
final Account account,
final Bundle options) throws NetworkErrorException {
return null;
}
/**
* Gets an authtoken for an account.
* <p>
* If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
* depending on whether a token was successfully issued and, if not, whether one could be issued
* via some {@link Activity}.
* <p>
* If a token cannot be provided without some additional activity, the Bundle should contain
* {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
* there is no such activity, then a Bundle containing {@link AccountManager#KEY_ERROR_CODE} and
* {@link AccountManager#KEY_ERROR_MESSAGE} should be returned.
* <p>
* If a token can be successfully issued, the implementation should return the {@link
* AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the account
* associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In addition
* {@link AbstractAccountAuthenticator} implementations that declare themselves {@code
* android:customTokens=true} may also provide a non-negative {@link #KEY_CUSTOM_TOKEN_EXPIRY}
* long value containing the expiration timestamp of the expiration time (in millis since the
* unix epoch).
* <p>
* Implementers should assume that tokens will be cached on the basis of account and
* authTokenType. The system may ignore the contents of the supplied options Bundle when
* determining to re-use a cached token. Furthermore, implementers should assume a supplied
* expiration time will be treated as non-binding advice.
* <p>
* Finally, note that for android:customTokens=false authenticators, tokens are cached
* indefinitely until some client calls {@link AccountManager#invalidateAuthToken(String,
* String)}.
*
* @param response to send the result back to the AccountManager, will never be null
* @param account the account whose credentials are to be retrieved, will never be null
* @param authTokenType the type of auth token to retrieve, will never be null
* @param options a Bundle of authenticator-specific options, may be null
* @return a Bundle result or null if the result is to be returned via the response.
* @throws NetworkErrorException if the authenticator could not honor the request due to a
* network error
*/
@Override
public Bundle getAuthToken(final AccountAuthenticatorResponse response, final Account account,
final String authTokenType, final Bundle options)
throws NetworkErrorException {
throw new UnsupportedOperationException();
}
/**
* Ask the authenticator for a localized label for the given authTokenType.
*
* @param authTokenType the authTokenType whose label is to be returned, will never be null
* @return the localized label of the auth token type, may be null if the type isn't known
*/
@Override
public String getAuthTokenLabel(final String authTokenType) {
throw new UnsupportedOperationException();
}
/**
* Update the locally stored credentials for an account.
*
* @param response to send the result back to the AccountManager, will never be null
* @param account the account whose credentials are to be updated, will never be null
* @param authTokenType the type of auth token to retrieve after updating the credentials, may
* be null
* @param options a Bundle of authenticator-specific options, may be null
* @return a Bundle result or null if the result is to be returned via the response. The result
* will contain either: <ul> <li> {@link AccountManager#KEY_INTENT}, or <li> {@link
* AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the account
* whose credentials were updated, or <li> {@link AccountManager#KEY_ERROR_CODE} and {@link
* AccountManager#KEY_ERROR_MESSAGE} to indicate an error </ul>
* @throws NetworkErrorException if the authenticator could not honor the request due to a
* network error
*/
@Override
public Bundle updateCredentials(final AccountAuthenticatorResponse response,
final Account account,
final String authTokenType, final Bundle options)
throws NetworkErrorException {
throw new UnsupportedOperationException();
}
/**
* Checks if the account supports all the specified authenticator specific features.
*
* @param response to send the result back to the AccountManager, will never be null
* @param account the account to check, will never be null
* @param features an array of features to check, will never be null
* @return a Bundle result or null if the result is to be returned via the response. The result
* will contain either: <ul> <li> {@link AccountManager#KEY_INTENT}, or <li> {@link
* AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, false otherwise
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error </ul>
* @throws NetworkErrorException if the authenticator could not honor the request due to a
* network error
*/
@Override
public Bundle hasFeatures(final AccountAuthenticatorResponse response, final Account account,
final String[] features) throws NetworkErrorException {
throw new UnsupportedOperationException();
}
}
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:smallIcon="@mipmap/ic_launcher" />
public class AuthenticatorService extends Service {
private Authenticator authenticator;
@Override
public void onCreate() {
authenticator = new Authenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
return authenticator.getIBinder();
}
}
class ConfigurationSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = ConfigurationSyncAdapter.class.getSimpleName();
ConfigurationSyncAdapter(final Context context, final boolean autoInitialize) {
super(context, autoInitialize);
}
ConfigurationSyncAdapter(final Context context, final boolean autoInitialize,
final boolean allowParallelSyncs) {
super(context, autoInitialize, allowParallelSyncs);
manager = new ApolloBackendConfigurationManager(context);
}
/**
* Perform a sync for this account. SyncAdapter-specific parameters may be specified in extras,
* which is guaranteed to not be null. Invocations of this method are guaranteed to be
* serialized.
*
* @param account the account that should be synced
* @param extras SyncAdapter-specific parameters
* @param authority the authority of this sync request
* @param provider a ContentProviderClient that points to the ContentProvider for this
* authority
* @param syncResult SyncAdapter-specific parameters
*/
@Override
public void onPerformSync(final Account account, final Bundle extras, final String authority,
final ContentProviderClient provider, final SyncResult syncResult) {
Log.i(TAG, "onPerformSync() was called");
// do networking stuff here
}
}
public class ConfigurationSyncAdapterService extends AmlService {
private static ConfigurationSyncAdapter syncAdapter = null;
private static final Object syncAdapterLock = new Object();
@Override
public void onCreate() {
super.onCreate();
/*
* Create the sync adapter as a singleton.
* Set the sync adapter as syncable
* Disallow parallel syncs
*/
synchronized (syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = new ConfigurationSyncAdapter(getApplicationContext(), true);
}
}
}
/**
* Return an object that allows the system to invoke
* the sync adapter.
*
*/
@Override
public IBinder onBind(final 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();
}
}
<resources>
<string name="app_name">AppName</string>
<string name="authority">com.packagename.authority</string>
<string name="account_type">some.account.type.com</string>
</resources>
public class StubContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs,
final String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull final Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull final Uri uri, final ContentValues values) {
return null;
}
@Override
public int delete(@NonNull final Uri uri, final String selection,
final String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull final Uri uri, final ContentValues values, final String selection,
final String[] selectionArgs) {
return 0;
}
}
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type"
android:allowParallelSyncs="false"
android:contentAuthority="@string/authority"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="false" />
class SyncAdapterManager {
private static final String TAG = SyncAdapterManager.class.getSimpleName();
private final String authority;
private final String type;
private Account account;
private Context context;
SyncAdapterManager(final Context context) {
this.context = context;
type = context.getString(R.string.account_type);
authority = context.getString(R.string.authority);
account = new Account(context.getString(R.string.app_name), type);
}
@SuppressWarnings ("MissingPermission")
void beginPeriodicSync(final long updateConfigInterval) {
Log.d(TAG, "beginPeriodicSync() called with: updateConfigInterval = [" +
updateConfigInterval + "]");
final AccountManager accountManager = (AccountManager) context
.getSystemService(ACCOUNT_SERVICE);
if (!accountManager.addAccountExplicitly(account, null, null)) {
account = accountManager.getAccountsByType(type)[0];
}
setAccountSyncable();
ContentResolver.addPeriodicSync(account, context.getString(R.string.authority),
Bundle.EMPTY, updateConfigInterval);
ContentResolver.setSyncAutomatically(account, authority, true);
}
void syncImmediately() {
Bundle settingsBundle = new Bundle();
settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(account, authority, settingsBundle);
}
private void setAccountSyncable() {
if (ContentResolver.getIsSyncable(account, authority) == 0) {
ContentResolver.setIsSyncable(account, authority, 1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment