fixed account details json serialization
This commit is contained in:
parent
fa92d313cc
commit
6d315e1c89
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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")'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package org.mariotaku.twidere.model
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/7.
|
||||
*/
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue