jenkins-bot has submitted this change and it was merged.
Change subject: Add user option sync adapter
......................................................................
Add user option sync adapter
Bug: T124350
Change-Id: Ibb3428ac4b35e114b3f753488f5dbc38ab029095
---
M app/src/alpha/res/values/strings_no_translate.xml
M app/src/beta/res/values/strings_no_translate.xml
M app/src/dev/res/values/strings_no_translate.xml
M app/src/main/AndroidManifest.xml
M app/src/main/java/org/wikipedia/WikipediaApp.java
M app/src/main/java/org/wikipedia/auth/AccountUtil.java
M app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
M app/src/main/java/org/wikipedia/database/sync/DefaultSyncRow.java
M app/src/main/java/org/wikipedia/database/sync/SyncRow.java
A app/src/main/java/org/wikipedia/database/sync/SyncRowDao.java
A app/src/main/java/org/wikipedia/dataclient/mwapi/MwPostResponse.java
M app/src/main/java/org/wikipedia/server/mwapi/MwServiceError.java
A app/src/main/java/org/wikipedia/useroption/database/UserOptionDao.java
M app/src/main/java/org/wikipedia/useroption/database/UserOptionRow.java
M
app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
A app/src/main/java/org/wikipedia/useroption/sync/UserOptionContentResolver.java
A app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
A app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncService.java
M app/src/main/res/values-qq/strings.xml
M app/src/main/res/values/strings.xml
A app/src/main/res/xml/user_option_sync_adapter.xml
21 files changed, 617 insertions(+), 24 deletions(-)
Approvals:
Dbrant: Looks good to me, approved
jenkins-bot: Verified
diff --git a/app/src/alpha/res/values/strings_no_translate.xml
b/app/src/alpha/res/values/strings_no_translate.xml
index 821c0a9..b353daa 100644
--- a/app/src/alpha/res/values/strings_no_translate.xml
+++ b/app/src/alpha/res/values/strings_no_translate.xml
@@ -7,4 +7,6 @@
<!-- The alpha build uses a different signature than beta and prod. -->
<string name="account_name">Wikimedia (Alpha)</string>
<string name="account_type">org.wikimedia.alpha</string>
+
+ <string name="user_option_sync_label">Preferences (Alpha)</string>
</resources>
diff --git a/app/src/beta/res/values/strings_no_translate.xml
b/app/src/beta/res/values/strings_no_translate.xml
index 9002593..e4ffb00 100644
--- a/app/src/beta/res/values/strings_no_translate.xml
+++ b/app/src/beta/res/values/strings_no_translate.xml
@@ -7,4 +7,6 @@
<!-- TODO: remove and use the same account as prod when tokens are stored.
-->
<string name="account_name">Wikimedia (Beta)</string>
<string name="account_type">org.wikimedia.beta</string>
-</resources>
+
+ <string name="user_option_sync_label">Preferences (Beta)</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/dev/res/values/strings_no_translate.xml
b/app/src/dev/res/values/strings_no_translate.xml
index 255c6c3..b0737e2 100644
--- a/app/src/dev/res/values/strings_no_translate.xml
+++ b/app/src/dev/res/values/strings_no_translate.xml
@@ -7,4 +7,6 @@
<!-- TODO: remove and use the same account as alpha when tokens are
stored. -->
<string name="account_name">Wikimedia (Dev)</string>
<string name="account_type">org.wikimedia.dev</string>
-</resources>
+
+ <string name="user_option_sync_label">Preferences (Dev)</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a6409e0..adb8eb0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -181,6 +181,13 @@
android:resource="@xml/file_paths" />
</provider>
+ <provider
+ android:authorities="@string/user_option_authority"
+ android:name=".useroption.database.UserOptionContentProvider"
+ android:exported="false"
+ android:syncable="true"
+ android:label="@string/user_option_sync_label" />
+
<receiver
android:icon="@mipmap/launcher"
android:label="@string/widget_name_search"
@@ -221,6 +228,20 @@
</receiver>
<service
+ android:name=".useroption.sync.UserOptionSyncService"
+ android:exported="false">
+
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/user_option_sync_adapter" />
+
+ </service>
+
+ <service
android:name=".auth.AuthenticatorService"
android:exported="false">
diff --git a/app/src/main/java/org/wikipedia/WikipediaApp.java
b/app/src/main/java/org/wikipedia/WikipediaApp.java
index 9cde2c3..099761f 100644
--- a/app/src/main/java/org/wikipedia/WikipediaApp.java
+++ b/app/src/main/java/org/wikipedia/WikipediaApp.java
@@ -42,7 +42,9 @@
import org.wikipedia.search.RecentSearch;
import org.wikipedia.settings.Prefs;
import org.wikipedia.theme.Theme;
+import org.wikipedia.useroption.database.UserOptionDao;
import org.wikipedia.useroption.database.UserOptionRow;
+import org.wikipedia.useroption.sync.UserOptionContentResolver;
import org.wikipedia.util.ApiUtil;
import org.wikipedia.util.ReleaseUtil;
import org.wikipedia.util.log.L;
@@ -185,6 +187,9 @@
// TODO: remove this code after all logged in users also have a system
account or August 2016.
AccountUtil.createAccountForLoggedInUser();
+
+ UserOptionContentResolver.requestManualSync();
+ UserOptionContentResolver.registerAppSyncObserver(this);
}
public Bus getBus() {
@@ -362,6 +367,7 @@
public void logOut() {
L.v("logging out");
AccountUtil.removeAccount();
+ UserOptionDao.instance().clear();
getEditTokenStorage().clearAllTokens();
getCookieManager().clearAllCookies();
getUserInfoStorage().clearUser();
@@ -458,6 +464,7 @@
if (theme != currentTheme) {
currentTheme = theme;
Prefs.setThemeId(currentTheme.getMarshallingId());
+ UserOptionDao.instance().theme(theme);
bus.post(new ThemeChangeEvent());
}
}
@@ -503,8 +510,11 @@
} else if (multiplier > FONT_SIZE_MULTIPLIER_MAX) {
multiplier = FONT_SIZE_MULTIPLIER_MAX;
}
- Prefs.setTextSizeMultiplier(multiplier);
- bus.post(new ChangeTextSizeEvent());
+ if (multiplier != Prefs.getTextSizeMultiplier()) {
+ Prefs.setTextSizeMultiplier(multiplier);
+ UserOptionDao.instance().fontSize(multiplier);
+ bus.post(new ChangeTextSizeEvent());
+ }
}
public void putCrashReportProperty(String key, String value) {
diff --git a/app/src/main/java/org/wikipedia/auth/AccountUtil.java
b/app/src/main/java/org/wikipedia/auth/AccountUtil.java
index 9c58e40..3365b16 100644
--- a/app/src/main/java/org/wikipedia/auth/AccountUtil.java
+++ b/app/src/main/java/org/wikipedia/auth/AccountUtil.java
@@ -12,6 +12,7 @@
import org.wikipedia.R;
import org.wikipedia.WikipediaApp;
import org.wikipedia.login.User;
+import org.wikipedia.useroption.sync.UserOptionContentResolver;
import org.wikipedia.util.ApiUtil;
import org.wikipedia.util.log.L;
@@ -39,6 +40,8 @@
response.onResult(bundle);
}
+
+ UserOptionContentResolver.requestManualSync();
} else {
if (response != null) {
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
"");
@@ -65,6 +68,10 @@
accountManager().removeAccount(account, null, null);
}
}
+ }
+
+ public static boolean supported(@NonNull Account account) {
+ return account.equals(AccountUtil.account());
}
@Nullable
@@ -101,4 +108,4 @@
}
private AccountUtil() { }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
b/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
index 11091b4..d00f6b9 100644
--- a/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
+++ b/app/src/main/java/org/wikipedia/auth/WikimediaAuthenticator.java
@@ -5,17 +5,21 @@
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import org.wikipedia.BuildConfig;
import org.wikipedia.R;
import org.wikipedia.analytics.LoginFunnel;
import org.wikipedia.login.LoginActivity;
public class WikimediaAuthenticator extends AbstractAccountAuthenticator {
+ private static final String[] SYNC_AUTHORITIES =
{BuildConfig.USER_OPTION_AUTHORITY};
+
@NonNull private final Context context;
public WikimediaAuthenticator(@NonNull Context context) {
@@ -104,4 +108,23 @@
return bundle;
}
-}
\ No newline at end of file
+
+ @Override
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse
response,
+ Account account) throws
NetworkErrorException {
+ Bundle result = super.getAccountRemovalAllowed(response, account);
+
+ if (result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
+ && !result.containsKey(AccountManager.KEY_INTENT)) {
+ boolean allowed =
result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+
+ if (allowed) {
+ for (String auth : SYNC_AUTHORITIES) {
+ ContentResolver.cancelSync(account, auth);
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/org/wikipedia/database/sync/DefaultSyncRow.java
b/app/src/main/java/org/wikipedia/database/sync/DefaultSyncRow.java
index 4bd332a..b7152a7 100644
--- a/app/src/main/java/org/wikipedia/database/sync/DefaultSyncRow.java
+++ b/app/src/main/java/org/wikipedia/database/sync/DefaultSyncRow.java
@@ -1,6 +1,7 @@
package org.wikipedia.database.sync;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
public class DefaultSyncRow implements SyncRow {
public static final int NO_TRANSACTION_ID = 0;
@@ -47,8 +48,11 @@
}
@Override
- public boolean isTransaction(@NonNull SyncRow row) {
- return transactionId() == row.transactionId();
+ public boolean completeable(@Nullable SyncRow old) {
+ boolean newer = old == null || transactionId() == NO_TRANSACTION_ID;
+ boolean response = old != null && transactionId() ==
old.transactionId();
+ boolean recordable = !(old == null && status() == SyncStatus.DELETED);
+ return (newer || response) && recordable;
}
@Override
diff --git a/app/src/main/java/org/wikipedia/database/sync/SyncRow.java
b/app/src/main/java/org/wikipedia/database/sync/SyncRow.java
index 050da57..4677041 100644
--- a/app/src/main/java/org/wikipedia/database/sync/SyncRow.java
+++ b/app/src/main/java/org/wikipedia/database/sync/SyncRow.java
@@ -1,6 +1,7 @@
package org.wikipedia.database.sync;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
public interface SyncRow {
@NonNull SyncStatus status();
@@ -9,6 +10,6 @@
void resetTransaction(@NonNull SyncStatus status);
void startTransaction();
- boolean isTransaction(@NonNull SyncRow row);
+ boolean completeable(@Nullable SyncRow old);
void completeTransaction(long timestamp);
}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/database/sync/SyncRowDao.java
b/app/src/main/java/org/wikipedia/database/sync/SyncRowDao.java
new file mode 100644
index 0000000..0283343
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/database/sync/SyncRowDao.java
@@ -0,0 +1,184 @@
+package org.wikipedia.database.sync;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.database.DatabaseClient;
+import org.wikipedia.useroption.database.UserOptionDatabaseTable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class SyncRowDao<T extends SyncRow> {
+ @NonNull private final DatabaseClient<T> client;
+ /**
+ * @param client Database client singleton. No writes should be performed
to the table outside
+ * of SyncRowDao.
+ */
+ public SyncRowDao(@NonNull DatabaseClient<T> client) {
+ this.client = client;
+ }
+
+ protected synchronized void upsert(@NonNull T item) {
+ T local = queryItem(item);
+ switch (local == null ? SyncStatus.ADDED : local.status()) {
+ case SYNCHRONIZED:
+ case OUTDATED:
+ case MODIFIED:
+ modifyTransaction(item);
+ break;
+ case ADDED:
+ case DELETED:
+ addTransaction(item);
+ break;
+ default:
+ throw new RuntimeException("status=" + item.status());
+ }
+ }
+
+ protected synchronized void update(@NonNull T item) {
+ T local = queryItem(item);
+ switch (local == null ? SyncStatus.SYNCHRONIZED : local.status()) {
+ case SYNCHRONIZED:
+ case MODIFIED:
+ case ADDED:
+ case DELETED:
+ insertTransaction(item, SyncStatus.OUTDATED);
+ break;
+ case OUTDATED:
+ break;
+ default:
+ throw new RuntimeException("status=" + item.status());
+ }
+ }
+
+ protected synchronized void delete(@NonNull T item) {
+ T local = queryItem(item);
+ switch (local == null ? SyncStatus.DELETED : local.status()) {
+ case SYNCHRONIZED:
+ case OUTDATED:
+ case MODIFIED:
+ case ADDED:
+ delete(item);
+ break;
+ case DELETED:
+ break;
+ default:
+ throw new RuntimeException("status=" + item.status());
+ }
+ }
+
+ /**
+ * Delete all table rows but don't update service state. For example, a
user logs out and all
+ * private data stored locally should be removed. If the sync adapter
account is not removed,
+ * the data may be repopulated.
+ */
+ public synchronized void clear() {
+ client.deleteAll();
+ }
+
+ public synchronized void reconcile(@NonNull T item) {
+ completeTransaction(item, System.currentTimeMillis());
+
+ // TODO: delete items no longer present in the database. The passed in
list of items is
+ // expected to be the full list of items available on the
service. After upserting,
+ // delete anything older than the current timestamp.
+ }
+
+ @NonNull public synchronized Collection<T> startTransaction() {
+ Collection<T> items = querySyncable();
+ for (T item : items) {
+ item.startTransaction();
+ insertItem(item);
+ }
+ return items;
+ }
+
+ public synchronized void resetTransaction(@NonNull T item) {
+ if (!completable(item)) {
+ return;
+ }
+
+ item.resetTransaction(item.status());
+ insertItem(item);
+ }
+
+ public void completeTransaction(@NonNull T item) {
+ long timestamp = System.currentTimeMillis();
+ completeTransaction(item, timestamp);
+ }
+
+ public synchronized void completeTransaction(@NonNull T item, long
timestamp) {
+ if (!completable(item)) {
+ return;
+ }
+
+ switch (item.status()) {
+ case SYNCHRONIZED:
+ case OUTDATED:
+ case MODIFIED:
+ case ADDED:
+ item.completeTransaction(timestamp);
+ insertItem(item);
+ break;
+ case DELETED:
+ removeItem(item);
+ break;
+ default:
+ throw new RuntimeException("status=" + item.status());
+ }
+ }
+
+ private boolean completable(@NonNull T item) {
+ T local = queryItem(item);
+ return item.completeable(local);
+ }
+
+ @NonNull private Collection<T> querySyncable() {
+ String[] selectionArgs = null;
+ String selection = UserOptionDatabaseTable.Col.SYNC_STATUS.getName() +
" != " + SyncStatus.SYNCHRONIZED.code() + " and "
+ + UserOptionDatabaseTable.Col.SYNC_TRANSACTION_ID.getName() +
" == " + DefaultSyncRow.NO_TRANSACTION_ID;
+ String sortOrder = null;
+ Cursor cursor = client.select(selection, selectionArgs, sortOrder);
+ return cursorToCollection(cursor);
+ }
+
+ @NonNull private Collection<T> cursorToCollection(@NonNull Cursor cursor) {
+ Collection<T> ret = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ ret.add(client.fromCursor(cursor));
+ }
+ return ret;
+ }
+
+ private void addTransaction(@NonNull T item) {
+ insertTransaction(item, SyncStatus.ADDED);
+ }
+
+ private void modifyTransaction(@NonNull T item) {
+ insertTransaction(item, SyncStatus.MODIFIED);
+ }
+
+ private void insertTransaction(@NonNull T item, @NonNull SyncStatus
status) {
+ item.resetTransaction(status);
+ insertItem(item);
+ }
+
+ @Nullable protected T queryItem(@NonNull T item) {
+ String[] selectionArgs = client.getPrimaryKeySelectionArgs(item);
+ String selection = client.getPrimaryKeySelection(item, selectionArgs);
+ String sortOrder = null;
+ Cursor cursor = client.select(selection, selectionArgs, sortOrder);
+ return cursor.moveToNext() ? client.fromCursor(cursor) : null;
+ }
+
+ private synchronized void removeItem(@NonNull T item) {
+ String[] selectionArgs = client.getPrimaryKeySelectionArgs(item);
+ client.delete(item, selectionArgs);
+ }
+
+ protected synchronized void insertItem(@NonNull T item) {
+ client.persist(item);
+ }
+}
diff --git
a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwPostResponse.java
b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwPostResponse.java
new file mode 100644
index 0000000..1a2f713
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwPostResponse.java
@@ -0,0 +1,26 @@
+package org.wikipedia.dataclient.mwapi;
+
+import android.support.annotation.Nullable;
+
+import org.wikipedia.server.mwapi.MwServiceError;
+
+public abstract class MwPostResponse {
+ @Nullable private String servedby;
+ @Nullable private MwServiceError error;
+
+ @Nullable public String code() {
+ return error == null ? null : error.getTitle();
+ }
+
+ @Nullable public String info() {
+ return error == null ? null : error.getDetails();
+ }
+
+ public boolean success(@Nullable String result) {
+ return error == null && "success".equals(result);
+ }
+
+ public boolean badToken() {
+ return error != null && error.badToken();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/wikipedia/server/mwapi/MwServiceError.java
b/app/src/main/java/org/wikipedia/server/mwapi/MwServiceError.java
index 3b43383..a3da521 100644
--- a/app/src/main/java/org/wikipedia/server/mwapi/MwServiceError.java
+++ b/app/src/main/java/org/wikipedia/server/mwapi/MwServiceError.java
@@ -22,6 +22,10 @@
return docref;
}
+ public boolean badToken() {
+ return "badtoken".equals(code);
+ }
+
@Override
public String toString() {
return "MwServiceError{"
diff --git
a/app/src/main/java/org/wikipedia/useroption/database/UserOptionDao.java
b/app/src/main/java/org/wikipedia/useroption/database/UserOptionDao.java
new file mode 100644
index 0000000..7511ccb
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/useroption/database/UserOptionDao.java
@@ -0,0 +1,106 @@
+package org.wikipedia.useroption.database;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.concurrency.SaneAsyncTask;
+import org.wikipedia.database.sync.SyncRowDao;
+import org.wikipedia.theme.Theme;
+import org.wikipedia.useroption.UserOption;
+import org.wikipedia.useroption.sync.UserOptionContentResolver;
+
+import java.util.Collection;
+
+public final class UserOptionDao extends SyncRowDao<UserOptionRow> {
+ public interface Callback<T> {
+ void success(@Nullable T item);
+ }
+
+ private static final String THEME_KEY = "userjs-app-pref-theme";
+ private static final String FONT_SIZE_KEY = "userjs-app-pref-font-size";
+
+ private static UserOptionDao INSTANCE = new UserOptionDao();
+
+ public static UserOptionDao instance() {
+ return INSTANCE;
+ }
+
+ public void theme(@NonNull Theme theme) {
+ new UpsertTask(new UserOptionRow(THEME_KEY,
String.valueOf(theme.isLight()))).execute();
+ }
+
+ public void theme(final Callback<Theme> callback) {
+ new QueryTask(THEME_KEY) {
+ @Override
+ public void onFinish(UserOptionRow result) {
+ callback.success(result == null
+ ? null
+ : Boolean.valueOf(result.val()) ? Theme.LIGHT :
Theme.DARK);
+ }
+ }.execute();
+ }
+
+ public void fontSize(int size) {
+ new UpsertTask(new UserOptionRow(FONT_SIZE_KEY,
String.valueOf(size))).execute();
+ }
+
+ public void fontSize(final Callback<Integer> callback) {
+ new QueryTask(FONT_SIZE_KEY) {
+ @Override
+ public void onFinish(UserOptionRow result) {
+ super.onFinish(result);
+ callback.success(result == null ? null :
Integer.valueOf(result.val()));
+ }
+ }.execute();
+ }
+
+ @Nullable private UserOptionRow queryItem(@NonNull String key) {
+ return queryItem(new UserOptionRow(key));
+ }
+
+ public void reconcileOptions(@NonNull Collection<UserOption> options) {
+ for (UserOption option : options) {
+ reconcile(new UserOptionRow(option));
+ }
+ }
+
+ private UserOptionDao() {
+
super(WikipediaApp.getInstance().getDatabaseClient(UserOptionRow.class));
+ }
+
+ // TODO: replace AsyncTasks with SQLBrite.
+
+ private class UpsertTask extends SaneAsyncTask<Void> {
+ @NonNull private final UserOptionRow row;
+
+ UpsertTask(@NonNull UserOptionRow row) {
+ this.row = row;
+ }
+
+ @Override
+ public Void performTask() throws Throwable {
+ upsert(row);
+ return null;
+ }
+
+ @Override
+ public void onFinish(Void result) {
+ super.onFinish(result);
+ UserOptionContentResolver.requestManualUpload();
+ }
+ }
+
+ private class QueryTask extends SaneAsyncTask<UserOptionRow> {
+ private final String key;
+
+ QueryTask(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public UserOptionRow performTask() throws Throwable {
+ return queryItem(key);
+ }
+ }
+}
\ No newline at end of file
diff --git
a/app/src/main/java/org/wikipedia/useroption/database/UserOptionRow.java
b/app/src/main/java/org/wikipedia/useroption/database/UserOptionRow.java
index 8a0454b..5734a36 100644
--- a/app/src/main/java/org/wikipedia/useroption/database/UserOptionRow.java
+++ b/app/src/main/java/org/wikipedia/useroption/database/UserOptionRow.java
@@ -61,8 +61,8 @@
}
@Override
- public boolean isTransaction(@NonNull SyncRow row) {
- return sync.isTransaction(row);
+ public boolean completeable(@NonNull SyncRow row) {
+ return sync.completeable(row);
}
@Override
diff --git
a/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
b/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
index dc1f918..e43c511 100644
---
a/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
+++
b/app/src/main/java/org/wikipedia/useroption/dataclient/DefaultUserOptionDataClient.java
@@ -8,6 +8,7 @@
import org.wikipedia.Site;
import org.wikipedia.WikipediaApp;
import org.wikipedia.dataclient.RestAdapterFactory;
+import org.wikipedia.dataclient.mwapi.MwPostResponse;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.editing.FetchEditTokenTask;
import org.wikipedia.useroption.UserOption;
@@ -38,12 +39,12 @@
@Override
public void post(@NonNull UserOption option) {
- client.post(getToken(), option.key(), option.val()).check();
+ client.post(getToken(), option.key(), option.val()).check(site);
}
@Override
public void delete(@NonNull UserOption option) {
- client.delete(getToken(), option.key()).check();
+ client.delete(getToken(), option.key()).check(site);
}
@NonNull private String getToken() {
@@ -72,7 +73,7 @@
}.execute();
}
- private WikipediaApp app() {
+ private static WikipediaApp app() {
return WikipediaApp.getInstance();
}
@@ -101,22 +102,21 @@
@Query("change") @NonNull String key);
}
- private static class PostResponse {
+ private static class PostResponse extends MwPostResponse {
private String options;
-
- public boolean success() {
- return "success".equals(options);
- }
public String result() {
return options;
}
- public void check() {
- if (!success()) {
- // TODO: pass actual URL (here and elsewhere). This class is
populated by Retrofit and
- // doesn't seem to be able to hold references to the
outter class' instance members.
- throw RetrofitError.unexpectedError("", new
RuntimeException("Bad response=" + result()));
+ public void check(@NonNull Site site) {
+ if (!success(options)) {
+ if (badToken()) {
+ app().getEditTokenStorage().token(site, null);
+ }
+
+ throw RetrofitError.unexpectedError(site.host(),
+ new RuntimeException("Bad response=" + result()));
}
}
}
diff --git
a/app/src/main/java/org/wikipedia/useroption/sync/UserOptionContentResolver.java
b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionContentResolver.java
new file mode 100644
index 0000000..453e217
--- /dev/null
+++
b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionContentResolver.java
@@ -0,0 +1,84 @@
+package org.wikipedia.useroption.sync;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.BuildConfig;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.auth.AccountUtil;
+import org.wikipedia.theme.Theme;
+import org.wikipedia.useroption.database.UserOptionDao;
+import org.wikipedia.useroption.database.UserOptionRow;
+import org.wikipedia.util.log.L;
+
+public final class UserOptionContentResolver {
+ public static void requestManualUpload() {
+ requestManualSync(true);
+ }
+
+ public static void requestManualSync() {
+ requestManualSync(false);
+ }
+
+ public static void requestManualSync(boolean uploadOnly) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ bundle.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadOnly);
+
+ requestSync(bundle);
+ }
+
+ public static void registerAppSyncObserver(@NonNull Context context) {
+ Uri uri = UserOptionRow.DATABASE_TABLE.getBaseContentURI();
+ UserOptionContentObserver observer = new UserOptionContentObserver();
+ context.getContentResolver().registerContentObserver(uri, true,
observer);
+ }
+
+ private static void requestSync(@NonNull Bundle bundle) {
+ Account account = AccountUtil.account();
+ if (account == null) {
+ L.i("no account");
+ return;
+ }
+
+ ContentResolver.requestSync(account,
BuildConfig.USER_OPTION_AUTHORITY, bundle);
+ }
+
+ private UserOptionContentResolver() { }
+
+ private static class UserOptionContentObserver extends ContentObserver {
+ UserOptionContentObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ UserOptionDao.instance().theme(new UserOptionDao.Callback<Theme>()
{
+ @Override
+ public void success(@Nullable Theme item) {
+ if (item != null) {
+ WikipediaApp.getInstance().setCurrentTheme(item);
+ }
+ }
+ });
+ UserOptionDao.instance().fontSize(new
UserOptionDao.Callback<Integer>() {
+ @Override
+ public void success(@Nullable Integer item) {
+ if (item != null) {
+ WikipediaApp.getInstance().setFontSizeMultiplier(item);
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git
a/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
new file mode 100644
index 0000000..8986dd2
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncAdapter.java
@@ -0,0 +1,76 @@
+package org.wikipedia.useroption.sync;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+import org.wikipedia.auth.AccountUtil;
+import org.wikipedia.database.sync.SyncStatus;
+import org.wikipedia.useroption.UserOption;
+import org.wikipedia.useroption.database.UserOptionDao;
+import org.wikipedia.useroption.database.UserOptionRow;
+import org.wikipedia.useroption.dataclient.UserInfo;
+import org.wikipedia.useroption.dataclient.UserOptionDataClientSingleton;
+import org.wikipedia.util.log.L;
+
+import java.util.Collection;
+
+import retrofit.RetrofitError;
+
+public class UserOptionSyncAdapter extends AbstractThreadedSyncAdapter {
+ public UserOptionSyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult
syncResult) {
+ if (!AccountUtil.supported(account)) {
+ L.i("unexpected account=" + account);
+ ++syncResult.stats.numAuthExceptions;
+ return;
+ }
+
+ boolean uploadOnly =
extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD);
+
+ try {
+ upload();
+ if (!uploadOnly) {
+ download();
+ }
+ } catch (RetrofitError e) {
+ L.d(e);
+ ++syncResult.stats.numIoExceptions;
+ }
+ }
+
+ private void download() {
+ UserInfo info = UserOptionDataClientSingleton.instance().get();
+ Collection<UserOption> options = info.userjsOptions();
+ L.i("downloaded " + options.size() + " option(s)");
+ UserOptionDao.instance().reconcileOptions(options);
+ }
+
+ private void upload() {
+ Collection<UserOptionRow> options =
UserOptionDao.instance().startTransaction();
+ L.i("uploading " + options.size() + " option(s)");
+ for (UserOptionRow option : options) {
+ try {
+ if (option.status() == SyncStatus.DELETED) {
+ UserOptionDataClientSingleton.instance().delete(option);
+ } else {
+ UserOptionDataClientSingleton.instance().post(option);
+ }
+ } catch (RetrofitError e) {
+ UserOptionDao.instance().resetTransaction(option);
+ throw e;
+ }
+
+ UserOptionDao.instance().completeTransaction(option);
+ }
+ }
+}
\ No newline at end of file
diff --git
a/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncService.java
b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncService.java
new file mode 100644
index 0000000..7c0b235
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/useroption/sync/UserOptionSyncService.java
@@ -0,0 +1,29 @@
+package org.wikipedia.useroption.sync;
+
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class UserOptionSyncService extends Service {
+ @NonNull private static final Object SYNC_ADAPTER_LOCK = new Object();
+ @Nullable private static AbstractThreadedSyncAdapter SYNC_ADAPTER;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ synchronized (SYNC_ADAPTER_LOCK) {
+ if (SYNC_ADAPTER == null) {
+ SYNC_ADAPTER = new
UserOptionSyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return SYNC_ADAPTER == null ? null :
SYNC_ADAPTER.getSyncAdapterBinder();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-qq/strings.xml
b/app/src/main/res/values-qq/strings.xml
index 95ceb1b..5574e29 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -381,4 +381,5 @@
<string name="article_menu_bar_bookmark">Describes the action performed when
pressing the bookmark button in the page toolbar underneath the leading
image.</string>
<string name="article_menu_bar_share">Describes the action performed when
pressing the share button in the page toolbar underneath the leading
image.</string>
<string name="article_menu_bar_navigate">Describes the action performed when
pressing the navigate button in the page toolbar underneath the leading
image.</string>
+ <string name="user_option_sync_label">Checkbox title for Wikimedia account
preference synchronization.</string>
</resources>
diff --git a/app/src/main/res/values/strings.xml
b/app/src/main/res/values/strings.xml
index af15834..f561488 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -307,4 +307,8 @@
<string name="article_menu_bar_share">Share the article link</string>
<string name="article_menu_bar_navigate">Navigate to the location of the
article</string>
<!-- /Article menu bar -->
+
+ <!-- User options -->
+ <string name="user_option_sync_label">Preferences</string>
+ <!-- /User options -->
</resources>
diff --git a/app/src/main/res/xml/user_option_sync_adapter.xml
b/app/src/main/res/xml/user_option_sync_adapter.xml
new file mode 100644
index 0000000..5d52882
--- /dev/null
+++ b/app/src/main/res/xml/user_option_sync_adapter.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<sync-adapter
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:contentAuthority="@string/user_option_authority"
+ android:accountType="@string/account_type"
+ android:supportsUploading="true"
+ android:isAlwaysSyncable="true" />
\ No newline at end of file
--
To view, visit https://gerrit.wikimedia.org/r/272802
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ibb3428ac4b35e114b3f753488f5dbc38ab029095
Gerrit-PatchSet: 7
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Niedzielski <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits