Skip to content
Merged
Original file line number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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) {
throw new RuntimeException(e);
}
}
editor.commit();
}
}
Loading