fixed account details json serialization

This commit is contained in:
Mariotaku Lee 2016-12-07 21:20:25 +08:00
parent fa92d313cc
commit 6d315e1c89
12 changed files with 253 additions and 74 deletions

View File

@ -6,32 +6,72 @@ import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import com.bluelinelabs.logansquare.LoganSquare;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.annotation.OnJsonParseComplete;
import com.bluelinelabs.logansquare.annotation.OnPreJsonSerialize;
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.hannesdorfmann.parcelableplease.annotation.ParcelableNoThanks;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import org.mariotaku.twidere.annotation.AccountType;
import org.mariotaku.twidere.model.account.AccountExtras;
import org.mariotaku.twidere.model.account.cred.Credentials;
import org.mariotaku.twidere.model.util.UserKeyConverter;
import org.mariotaku.twidere.util.model.AccountDetailsUtils;
import java.io.IOException;
/**
* Created by mariotaku on 2016/12/3.
*/
@ParcelablePlease
@JsonObject
public class AccountDetails implements Parcelable, Comparable<AccountDetails> {
public boolean dummy;
@JsonField(name = "account", typeConverter = AccountConverter.class)
public Account account;
@JsonField(name = "key", typeConverter = UserKeyConverter.class)
public UserKey key;
public Credentials credentials;
public ParcelableUser user;
@ColorInt
public int color;
public int position;
public boolean activated;
@AccountType
@JsonField(name = "type")
public String type;
@Credentials.Type
@JsonField(name = "credentials_type")
public String credentials_type;
@JsonField(name = "user")
public ParcelableUser user;
@ColorInt
@JsonField(name = "color")
public int color;
@JsonField(name = "position")
public int position;
@JsonField(name = "activated")
public boolean activated;
@JsonField(name = "dummy")
public boolean dummy;
@JsonField(name = "credentials")
@ParcelableNoThanks
String credentials_json;
public Credentials credentials;
@JsonField(name = "extras")
@ParcelableNoThanks
String extras_json;
public AccountExtras extras;
@Override
@ -73,6 +113,26 @@ public class AccountDetails implements Parcelable, Comparable<AccountDetails> {
return dummy;
}
@OnPreJsonSerialize
void onPreJsonSerialize() throws IOException {
if (credentials != null) {
credentials_json = LoganSquare.serialize(credentials);
}
if (extras != null) {
extras_json = LoganSquare.serialize(extras);
}
}
@OnJsonParseComplete
void onJsonParseComplete() throws IOException {
if (credentials_json != null && credentials_type != null) {
credentials = AccountDetailsUtils.parseCredentials(credentials_json, credentials_type);
}
if (extras_json != null && type != null) {
extras = AccountDetailsUtils.parseAccountExtras(extras_json, type);
}
}
public static final Creator<AccountDetails> CREATOR = new Creator<AccountDetails>() {
public AccountDetails createFromParcel(Parcel source) {
AccountDetails target = new AccountDetails();
@ -84,4 +144,52 @@ public class AccountDetails implements Parcelable, Comparable<AccountDetails> {
return new AccountDetails[size];
}
};
static class AccountConverter implements TypeConverter<Account> {
@Override
public Account parse(JsonParser jsonParser) throws IOException {
if (jsonParser.getCurrentToken() == null) {
jsonParser.nextToken();
}
if (jsonParser.getCurrentToken() != JsonToken.START_OBJECT) {
jsonParser.skipChildren();
return null;
}
String name = null, type = null;
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = jsonParser.getCurrentName();
jsonParser.nextToken();
switch (fieldName) {
case "name": {
name = jsonParser.getValueAsString(null);
break;
}
case "type": {
type = jsonParser.getValueAsString(null);
break;
}
}
jsonParser.skipChildren();
}
if (name != null && type != null) {
return new Account(name, type);
}
return null;
}
@Override
public void serialize(Account object, String fieldName, boolean writeFieldNameForObject, JsonGenerator jsonGenerator) throws IOException {
if (writeFieldNameForObject) {
jsonGenerator.writeFieldName(fieldName);
}
if (object == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", object.name);
jsonGenerator.writeStringField("type", object.type);
jsonGenerator.writeEndObject();
}
}
}
}

View File

@ -81,11 +81,11 @@ public class MediaUploadResult implements Parcelable {
MediaUploadResultParcelablePlease.writeToParcel(this, dest, flags);
}
public static MediaUploadResult getInstance(final int errorCode, final String errorMessage) {
public static MediaUploadResult error(final int errorCode, final String errorMessage) {
return new MediaUploadResult(errorCode, errorMessage);
}
public static MediaUploadResult getInstance(final String... mediaUris) {
public static MediaUploadResult uploaded(final String... mediaUris) {
return new MediaUploadResult(mediaUris);
}
}

View File

@ -0,0 +1,61 @@
package org.mariotaku.twidere.util.model;
import com.bluelinelabs.logansquare.LoganSquare;
import org.mariotaku.twidere.annotation.AccountType;
import org.mariotaku.twidere.model.account.AccountExtras;
import org.mariotaku.twidere.model.account.StatusNetAccountExtras;
import org.mariotaku.twidere.model.account.TwitterAccountExtras;
import org.mariotaku.twidere.model.account.cred.BasicCredentials;
import org.mariotaku.twidere.model.account.cred.Credentials;
import org.mariotaku.twidere.model.account.cred.EmptyCredentials;
import org.mariotaku.twidere.model.account.cred.OAuth2Credentials;
import org.mariotaku.twidere.model.account.cred.OAuthCredentials;
import java.io.IOException;
/**
* Created by mariotaku on 2016/12/7.
*/
public class AccountDetailsUtils {
public static Credentials parseCredentials(String json, @Credentials.Type String type) {
try {
switch (type) {
case Credentials.Type.OAUTH:
case Credentials.Type.XAUTH: {
return LoganSquare.parse(json, OAuthCredentials.class);
}
case Credentials.Type.BASIC: {
return LoganSquare.parse(json, BasicCredentials.class);
}
case Credentials.Type.EMPTY: {
return LoganSquare.parse(json, EmptyCredentials.class);
}
case Credentials.Type.OAUTH2: {
return LoganSquare.parse(json, OAuth2Credentials.class);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
throw new UnsupportedOperationException(type);
}
public static AccountExtras parseAccountExtras(String json, @AccountType String type) {
if (json == null) return null;
try {
switch (type) {
case AccountType.TWITTER: {
return LoganSquare.parse(json, TwitterAccountExtras.class);
}
case AccountType.STATUSNET: {
return LoganSquare.parse(json, StatusNetAccountExtras.class);
}
}
} catch (IOException e) {
return null;
}
return null;
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="status_shortener_service_interface_version">4</string>
<string name="media_uploader_service_interface_version">4</string>
<string name="status_shortener_service_interface_version">5</string>
<string name="media_uploader_service_interface_version">5</string>
</resources>

View File

@ -40,24 +40,16 @@ import android.support.annotation.RequiresPermission;
import com.bluelinelabs.logansquare.LoganSquare;
import org.mariotaku.twidere.annotation.AccountType;
import org.mariotaku.twidere.model.AccountDetails;
import org.mariotaku.twidere.model.ComposingStatus;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.account.AccountExtras;
import org.mariotaku.twidere.model.account.StatusNetAccountExtras;
import org.mariotaku.twidere.model.account.TwitterAccountExtras;
import org.mariotaku.twidere.model.account.cred.BasicCredentials;
import org.mariotaku.twidere.model.account.cred.Credentials;
import org.mariotaku.twidere.model.account.cred.EmptyCredentials;
import org.mariotaku.twidere.model.account.cred.OAuth2Credentials;
import org.mariotaku.twidere.model.account.cred.OAuthCredentials;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.DNS;
import org.mariotaku.twidere.provider.TwidereDataStore.Permissions;
import org.mariotaku.twidere.util.model.AccountDetailsUtils;
import java.io.IOException;
import java.lang.annotation.Retention;
@ -281,12 +273,12 @@ public final class Twidere implements TwidereConstants {
details.activated = Boolean.parseBoolean(am.getUserData(account, ACCOUNT_USER_DATA_ACTIVATED));
try {
details.credentials = parseCredentials(am.peekAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE),
details.credentials = AccountDetailsUtils.parseCredentials(am.peekAuthToken(account, ACCOUNT_AUTH_TOKEN_TYPE),
details.credentials_type);
} catch (SecurityException e) {
// Ignore
}
details.extras = parseAccountExtras(am.getUserData(account, ACCOUNT_USER_DATA_EXTRAS), details.type);
details.extras = AccountDetailsUtils.parseAccountExtras(am.getUserData(account, ACCOUNT_USER_DATA_EXTRAS), details.type);
details.user.color = details.color;
@ -299,46 +291,6 @@ public final class Twidere implements TwidereConstants {
return UserKey.valueOf(am.getUserData(account, ACCOUNT_USER_DATA_KEY));
}
private static Credentials parseCredentials(String json, @Credentials.Type String type) {
try {
switch (type) {
case Credentials.Type.OAUTH:
case Credentials.Type.XAUTH: {
return LoganSquare.parse(json, OAuthCredentials.class);
}
case Credentials.Type.BASIC: {
return LoganSquare.parse(json, BasicCredentials.class);
}
case Credentials.Type.EMPTY: {
return LoganSquare.parse(json, EmptyCredentials.class);
}
case Credentials.Type.OAUTH2: {
return LoganSquare.parse(json, OAuth2Credentials.class);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
throw new UnsupportedOperationException(type);
}
private static AccountExtras parseAccountExtras(String json, @AccountType String type) {
if (json == null) return null;
try {
switch (type) {
case AccountType.TWITTER: {
return LoganSquare.parse(json, TwitterAccountExtras.class);
}
case AccountType.STATUSNET: {
return LoganSquare.parse(json, StatusNetAccountExtras.class);
}
}
} catch (IOException e) {
return null;
}
return null;
}
private static int indexOf(String[] input, String find) {
for (int i = 0, inputLength = input.length; i < inputLength; i++) {
if (find == null) {

View File

@ -35,8 +35,8 @@ android {
applicationId "org.mariotaku.twidere"
minSdkVersion 14
targetSdkVersion 25
versionCode 223
versionName '3.3.5'
versionCode 224
versionName '3.3.6'
multiDexEnabled true
buildConfigField 'boolean', 'LEAK_CANARY_ENABLED', 'Boolean.parseBoolean("false")'

View File

@ -0,0 +1,5 @@
package org.mariotaku.twidere.model
/**
* Created by mariotaku on 2016/12/7.
*/

View File

@ -0,0 +1,42 @@
package org.mariotaku.twidere.util
import android.app.Application
import android.content.Context
import android.support.test.InstrumentationRegistry
import android.support.test.filters.FlakyTest
import android.support.test.runner.AndroidJUnit4
import android.text.TextUtils
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
/**
* Created by mariotaku on 2016/12/7.
*/
@RunWith(AndroidJUnit4::class)
class StatusShortenerInterfaceTest {
@Test
@FlakyTest
fun testConnection() {
val context = InstrumentationRegistry.getTargetContext();
val application = context.applicationContext as Application
val preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
val shortenerComponent = preferences.getString(TwidereConstants.KEY_STATUS_SHORTENER, null) ?: return
val instance = StatusShortenerInterface.getInstance(application, shortenerComponent)
instance.checkService { metaData ->
if (metaData == null) throw UpdateStatusTask.ExtensionVersionMismatchException()
val extensionVersion = metaData.getString(TwidereConstants.METADATA_KEY_EXTENSION_VERSION_STATUS_SHORTENER)
if (!TextUtils.equals(extensionVersion, context.getString(R.string.status_shortener_service_interface_version))) {
throw UpdateStatusTask.ExtensionVersionMismatchException()
}
}
Assert.assertTrue(instance.waitForService())
instance.unbindService()
}
}

View File

@ -27,9 +27,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.support.annotation.Nullable;
import android.util.Log;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.constant.IntentConstants;
import org.mariotaku.twidere.util.ServiceUtils.ServiceToken;
@ -42,23 +40,27 @@ public abstract class AbsServiceInterface<I extends IInterface> implements IInte
private I mIInterface;
private ServiceToken mToken;
private boolean mDisconnected;
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName service, final IBinder obj) {
mIInterface = AbsServiceInterface.this.onServiceConnected(service, obj);
mDisconnected = false;
}
@Override
public void onServiceDisconnected(final ComponentName service) {
mIInterface = null;
mDisconnected = true;
}
};
protected abstract I onServiceConnected(ComponentName service, IBinder obj);
protected AbsServiceInterface(final Context context, final String componentName, @Nullable final Bundle metaData) {
mDisconnected = true;
mContext = context;
mShortenerName = componentName;
mMetaData = metaData;
@ -75,21 +77,27 @@ public abstract class AbsServiceInterface<I extends IInterface> implements IInte
}
public final void unbindService() {
if (mIInterface == null || mToken == null) return;
ServiceUtils.unbindFromService(mToken);
}
public final void waitForService() {
public final boolean waitForService() {
if (mIInterface != null || mToken != null) return true;
final Intent intent = new Intent(IntentConstants.INTENT_ACTION_EXTENSION_SHORTEN_STATUS);
final ComponentName component = ComponentName.unflattenFromString(mShortenerName);
intent.setComponent(component);
mDisconnected = true;
mToken = ServiceUtils.bindToService(mContext, intent, mConnection);
while (mIInterface == null) {
if (mToken == null) return false;
while (mIInterface == null && !mDisconnected) {
try {
Thread.sleep(100L);
} catch (final InterruptedException e) {
Log.w(TwidereConstants.LOGTAG, e);
Thread.sleep(50L);
} catch (InterruptedException e) {
// Ignore
return false;
}
}
return true;
}
public final void checkService(CheckServiceAction action) throws CheckServiceException {

View File

@ -52,7 +52,7 @@ public final class MediaUploaderInterface extends AbsServiceInterface<IMediaUplo
final UserKey currentAccountKey,
final UploaderMediaItem[] media) {
final IMediaUploader iface = getInterface();
if (iface == null) return null;
if (iface == null) return MediaUploadResult.error(1, "Uploader not ready");
try {
final String statusJson = JsonSerializer.serialize(status, ParcelableStatusUpdate.class);
final String mediaJson = JsonSerializer.serialize(media, UploaderMediaItem.class);

View File

@ -56,7 +56,7 @@ public final class StatusShortenerInterface extends AbsServiceInterface<IStatusS
final UserKey currentAccountId,
final String overrideStatusText) {
final IStatusShortener iface = getInterface();
if (iface == null) return null;
if (iface == null) return StatusShortenResult.error(1, "Shortener not ready");
try {
final String statusJson = JsonSerializer.serialize(status, ParcelableStatusUpdate.class);
final String resultJson = iface.shorten(statusJson, currentAccountId.toString(),

View File

@ -149,6 +149,7 @@ class UpdateStatusTask(
private fun uploadMediaWithExtension(uploader: MediaUploaderInterface,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
uploader.waitForService()
val media: Array<UploaderMediaItem>
try {
media = UploaderMediaItem.getFromStatusUpdate(context, update)
@ -184,7 +185,7 @@ class UpdateStatusTask(
private fun shortenStatus(shortener: StatusShortenerInterface?,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
if (shortener == null) return
if (shortener == null || !shortener.waitForService()) return
stateCallback.onShorteningStatus()
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
for (i in 0..pending.length - 1) {
@ -347,6 +348,7 @@ class UpdateStatusTask(
private fun statusShortenCallback(shortener: StatusShortenerInterface?, pendingUpdate: PendingStatusUpdate, updateResult: UpdateStatusResult) {
if (shortener == null) return
shortener.waitForService()
for (i in 0..pendingUpdate.length - 1) {
val shortenResult = pendingUpdate.statusShortenResults[i]
val status = updateResult.statuses[i]
@ -357,6 +359,7 @@ class UpdateStatusTask(
private fun mediaUploadCallback(uploader: MediaUploaderInterface?, pendingUpdate: PendingStatusUpdate, updateResult: UpdateStatusResult) {
if (uploader == null) return
uploader.waitForService()
for (i in 0..pendingUpdate.length - 1) {
val uploadResult = pendingUpdate.mediaUploadResults[i]
val status = updateResult.statuses[i]