Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
package io.ably.lib.push;

import android.content.Context;
import android.test.AndroidTestCase;
import io.ably.lib.types.RegistrationToken;
import junit.extensions.TestSetup;
import junit.framework.TestSuite;
import org.junit.BeforeClass;

import java.lang.reflect.Field;
import java.util.HashMap;

public class LocalDeviceStorageTest extends AndroidTestCase {
private Context context;
private ActivationContext activationContext;


private HashMap<String, Object> hashMap = new HashMap<>();

private Storage inMemoryStorage = new Storage() {
@Override
public void put(String key, String value) {
hashMap.put(key, value);
}

@Override
public void put(String key, int value) {
hashMap.put(key, value);
}

@Override
public String get(String key, String defaultValue) {
Object value = hashMap.get(key);
return value != null ? (String) value : defaultValue;
}

@Override
public int get(String key, int defaultValue) {
Object value = hashMap.get(key);
return value != null ? (int) value : defaultValue;
}

@Override
public void clear(Field[] fields) {
hashMap = new HashMap<>();
}
};

@BeforeClass
public void setUp() {
context = getContext();
activationContext = new ActivationContext(context.getApplicationContext());
}

public static junit.framework.Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new TestSetup(new TestSuite(LocalDeviceStorageTest.class)) {});
return suite;
}

public void test_shared_preferences_storage_used_by_default() {
LocalDevice localDevice = new LocalDevice(activationContext, null);
/* initialize properties in storage */
localDevice.create();

/* verify custom storage is not used */
assertTrue(hashMap.isEmpty());

/* load properties */
assertNotNull(localDevice.id);
assertNotNull(localDevice.deviceSecret);
}

public void test_shared_preferences_storage_works_correctly() {
LocalDevice localDevice = new LocalDevice(activationContext, null);

RegistrationToken registrationToken= new RegistrationToken(RegistrationToken.Type.FCM, "ABLY");
/* initialize properties in storage */
localDevice.create();
localDevice.setAndPersistRegistrationToken(registrationToken);

/* verify custom storage is not used */
assertTrue(hashMap.isEmpty());

/* load properties */
assertNotNull(localDevice.id);
assertNotNull(localDevice.deviceSecret);
assertTrue(localDevice.isCreated());
assertEquals("FCM", localDevice.getRegistrationToken().type.name());
assertEquals("ABLY", localDevice.getRegistrationToken().token);

/* reset all properties */
localDevice.reset();

/* properties were cleared */
assertNull(localDevice.id);
assertNull(localDevice.deviceSecret);
assertNull(localDevice.getRegistrationToken());
}

public void test_custom_storage_used_if_provided() {
LocalDevice localDevice = new LocalDevice(activationContext, inMemoryStorage);
/* initialize properties in storage */
localDevice.create();

/* verify in memory storage is used */
assertFalse(hashMap.isEmpty());

/* load properties */
assertNotNull(localDevice.id);
assertNotNull(localDevice.deviceSecret);

String deviceId = localDevice.id;
String deviceSecret = localDevice.deviceSecret;

/* values are the same */
assertEquals(deviceId, hashMap.get("ABLY_DEVICE_ID"));
assertEquals(deviceSecret, hashMap.get("ABLY_DEVICE_SECRET"));
}

public void test_custom_storage_works_correctly() {
LocalDevice localDevice = new LocalDevice(activationContext, inMemoryStorage);

RegistrationToken registrationToken= new RegistrationToken(RegistrationToken.Type.FCM, "ABLY");
/* initialize properties in storage */
localDevice.create();
localDevice.setAndPersistRegistrationToken(registrationToken);

/* verify custom storage is used */
assertFalse(hashMap.isEmpty());

/* load properties */
assertNotNull(localDevice.id);
assertNotNull(localDevice.deviceSecret);
assertTrue(localDevice.isCreated());
assertEquals("FCM", localDevice.getRegistrationToken().type.name());
assertEquals("ABLY", localDevice.getRegistrationToken().token);

/* reset all properties */
localDevice.reset();
/* verify custom storage was cleared out */
assertTrue(hashMap.isEmpty());

/* properties were cleared */
assertNull(localDevice.id);
assertNull(localDevice.deviceSecret);
assertNull(localDevice.getRegistrationToken());
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,7 +30,9 @@ Context getContext() {
public synchronized LocalDevice getLocalDevice() {
if(localDevice == null) {
Log.v(TAG, "getLocalDevice(): creating new instance and returning that");
localDevice = new LocalDevice(this);
Storage storage = ably != null ? ably.options.localStorage : null;

localDevice = new LocalDevice(this, storage);
} else {
Log.v(TAG, "getLocalDevice(): returning existing instance");
}
Expand Down
70 changes: 23 additions & 47 deletions android/src/main/java/io/ably/lib/push/LocalDevice.java
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
package io.ably.lib.push;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;

import com.google.gson.JsonObject;

import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import io.ably.lib.rest.DeviceDetails;
import io.ably.lib.types.AblyException;
import io.ably.lib.types.Param;
import io.ably.lib.types.RegistrationToken;
import io.ably.lib.util.Base64Coder;
import io.ably.lib.util.Log;
import io.ably.lib.types.Param;
import io.azam.ulidj.ULID;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class LocalDevice extends DeviceDetails {
public String deviceSecret;
public String deviceIdentityToken;
private final Storage storage;

private final ActivationContext activationContext;

public LocalDevice(ActivationContext activationContext) {
public LocalDevice(ActivationContext activationContext, Storage storage) {
super();
Log.v(TAG, "LocalDevice(): initialising");
this.platform = "android";
this.formFactor = isTablet(activationContext.getContext()) ? "tablet" : "phone";
this.activationContext = activationContext;
this.push = new DeviceDetails.Push();
this.storage = storage != null ? storage : new SharedPreferenceStorage(activationContext);
loadPersisted();
}

Expand All@@ -47,26 +43,24 @@ public JsonObject toJsonObject() {

private void loadPersisted() {
/* Spec: RSH8a */
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());

String id = prefs.getString(SharedPrefKeys.DEVICE_ID, null);
String id = storage.get(SharedPrefKeys.DEVICE_ID, null);
this.id = id;
if(id != null) {
Log.v(TAG, "loadPersisted(): existing deviceId found; id: " + id);
deviceSecret = prefs.getString(SharedPrefKeys.DEVICE_SECRET, null);
deviceSecret = storage.get(SharedPrefKeys.DEVICE_SECRET, null);
} else {
Log.v(TAG, "loadPersisted(): existing deviceId not found.");
}
this.clientId = prefs.getString(SharedPrefKeys.CLIENT_ID, null);
this.deviceIdentityToken = prefs.getString(SharedPrefKeys.DEVICE_TOKEN, null);
this.clientId = storage.get(SharedPrefKeys.CLIENT_ID, null);
this.deviceIdentityToken = storage.get(SharedPrefKeys.DEVICE_TOKEN, null);

RegistrationToken.Type type = RegistrationToken.Type.fromOrdinal(
prefs.getInt(SharedPrefKeys.TOKEN_TYPE, -1));
storage.get(SharedPrefKeys.TOKEN_TYPE, -1));

Log.d(TAG, "loadPersisted(): token type = " + type);
if(type != null) {
RegistrationToken token = null;
String tokenString = prefs.getString(SharedPrefKeys.TOKEN, null);
String tokenString = storage.get(SharedPrefKeys.TOKEN, null);
Log.d(TAG, "loadPersisted(): token string = " + tokenString);
if(tokenString != null) {
token = new RegistrationToken(type, tokenString);
Expand DownExpand Up@@ -103,42 +97,32 @@ private void clearRegistrationToken() {
void setAndPersistRegistrationToken(RegistrationToken token) {
Log.v(TAG, "setAndPersistRegistrationToken(): token=" + token);
setRegistrationToken(token);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());
prefs.edit()
.putInt(SharedPrefKeys.TOKEN_TYPE, token.type.ordinal())
.putString(SharedPrefKeys.TOKEN, token.token)
.apply();
storage.put(SharedPrefKeys.TOKEN_TYPE, token.type.ordinal());
storage.put(SharedPrefKeys.TOKEN, token.token);
}

void setClientId(String clientId) {
Log.v(TAG, "setClientId(): clientId=" + clientId);
this.clientId = clientId;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());
prefs.edit().putString(SharedPrefKeys.CLIENT_ID, clientId).apply();
storage.put(SharedPrefKeys.CLIENT_ID, clientId);
}

public void setDeviceIdentityToken(String token) {
Log.v(TAG, "setDeviceIdentityToken(): token=" + token);
this.deviceIdentityToken = token;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());
prefs.edit().putString(SharedPrefKeys.DEVICE_TOKEN, token).apply();
storage.put(SharedPrefKeys.DEVICE_TOKEN, token);
}

boolean isCreated() {
return id != null;
}

boolean create() {
void create() {
/* Spec: RSH8b */
Log.v(TAG, "create()");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());
SharedPreferences.Editor editor = prefs.edit();

editor.putString(SharedPrefKeys.DEVICE_ID, (id = ULID.random()));
editor.putString(SharedPrefKeys.CLIENT_ID, (clientId = activationContext.clientId));
editor.putString(SharedPrefKeys.DEVICE_SECRET, (deviceSecret = generateSecret()));

return editor.commit();
storage.put(SharedPrefKeys.DEVICE_ID, (id = ULID.random()));
storage.put(SharedPrefKeys.CLIENT_ID, (clientId = activationContext.clientId));
storage.put(SharedPrefKeys.DEVICE_SECRET, (deviceSecret = generateSecret()));
}

public void reset() {
Expand All@@ -149,15 +133,7 @@ public void reset() {
this.clientId = null;
this.clearRegistrationToken();

SharedPreferences.Editor editor = activationContext.getPreferences().edit();
for (Field f : SharedPrefKeys.class.getDeclaredFields()) {
try {
editor.remove((String) f.get(null));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
editor.commit();
storage.clear(SharedPrefKeys.class.getDeclaredFields());
}

boolean isRegistered() {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
package io.ably.lib.push;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import java.lang.reflect.Field;

public class SharedPreferenceStorage implements Storage{

private final ActivationContext activationContext;

public SharedPreferenceStorage(ActivationContext activationContext) {
this.activationContext = activationContext;
}

private SharedPreferences sharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(activationContext.getContext());
}

@Override
public void put(String key, String value) {
sharedPreferences().edit().putString(key, value).apply();
}

@Override
public void put(String key, int value) {
sharedPreferences().edit().putInt(key, value).apply();
}

@Override
public String get(String key, String defaultValue) {
return sharedPreferences().getString(key, defaultValue);
}

@Override
public int get(String key, int defaultValue) {
return sharedPreferences().getInt(key, defaultValue);
}

@Override
public void clear(Field[] fields) {
SharedPreferences.Editor editor = activationContext.getPreferences().edit();
for (Field f : fields) {
try {
editor.remove((String) f.get(null));
} catch (IllegalAccessException e) {
Comment thread
QuintinWillison marked this conversation as resolved.
throw new RuntimeException(e);
}
}
editor.commit();
}
}
Loading