/*
* DefaultConnectableDeviceStore
* Connect SDK
*
* Copyright (c) 2014 LG Electronics.
* Created by Hyun Kook Khang on 19 Jan 2014
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.connectsdk.device;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import com.connectsdk.core.Util;
import com.connectsdk.service.DeviceService;
import com.connectsdk.service.config.ServiceConfig;
import com.connectsdk.service.config.ServiceDescription;
/**
* Default implementation of ConnectableDeviceStore. It stores data in a file in application
* data directory.
*/
public class DefaultConnectableDeviceStore implements ConnectableDeviceStore {
// @cond INTERNAL
public static final String KEY_VERSION = "version";
public static final String KEY_CREATED = "created";
public static final String KEY_UPDATED = "updated";
public static final String KEY_DEVICES = "devices";
static final int CURRENT_VERSION = 0;
static final String DIRPATH = "/android/data/connect_sdk/";
static final String FILENAME = "StoredDevices";
static final String IP_ADDRESS = "ipAddress";
static final String FRIENDLY_NAME = "friendlyName";
static final String MODEL_NAME = "modelName";
static final String MODEL_NUMBER = "modelNumber";
static final String SERVICES = "services";
static final String DESCRIPTION = "description";
static final String CONFIG = "config";
static final String FILTER = "filter";
static final String UUID = "uuid";
static final String PORT = "port";
static final String SERVICE_UUID = "serviceUUID";
static final String CLIENT_KEY = "clientKey";
static final String SERVER_CERTIFICATE = "serverCertificate";
static final String PAIRING_KEY = "pairingKey";
static final String DEFAULT_SERVICE_WEBOSTV = "WebOSTVService";
static final String DEFAULT_SERVICE_NETCASTTV = "NetcastTVService";
// @endcond
/** Date (in seconds from 1970) that the ConnectableDeviceStore was created. */
public long created;
/** Date (in seconds from 1970) that the ConnectableDeviceStore was last updated. */
public long updated;
/** Current version of the ConnectableDeviceStore, may be necessary for migrations */
public int version;
/**
* Max length of time for a ConnectableDevice to remain in the ConnectableDeviceStore without being discovered. Default is 3 days, and modifications to this value will trigger a scan for old devices.
*/
public long maxStoreDuration = TimeUnit.DAYS.toSeconds(3);
// @cond INTERNAL
private String fileFullPath;
private Map<String, JSONObject> storedDevices = new ConcurrentHashMap<String, JSONObject>();
private Map<String, ConnectableDevice> activeDevices = new ConcurrentHashMap<String, ConnectableDevice>();
private boolean waitToWrite = false;
public DefaultConnectableDeviceStore(Context context) {
String dirPath;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
dirPath = Environment.getExternalStorageDirectory().getAbsolutePath();
}
else {
dirPath = Environment.MEDIA_UNMOUNTED;
}
fileFullPath = dirPath + DIRPATH + FILENAME;
try {
fileFullPath = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.dataDir + "/" + FILENAME;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
load();
}
// @endcond
@Override
public void addDevice(ConnectableDevice device) {
if (device == null || device.getServices().size() == 0)
return;
if (!activeDevices.containsKey(device.getId()))
activeDevices.put(device.getId(), device);
JSONObject storedDevice = getStoredDevice(device.getId());
if (storedDevice != null) {
updateDevice(device);
} else {
storedDevices.put(device.getId(), device.toJSONObject());
store();
}
}
@Override
public void removeDevice(ConnectableDevice device) {
if (device == null)
return;
activeDevices.remove(device.getId());
storedDevices.remove(device.getId());
store();
}
@Override
public void updateDevice(ConnectableDevice device) {
if (device == null || device.getServices().size() == 0)
return;
JSONObject storedDevice = getStoredDevice(device.getId());
if (storedDevice == null)
return;
try {
storedDevice.put(ConnectableDevice.KEY_LAST_IP, device.getLastKnownIPAddress());
storedDevice.put(ConnectableDevice.KEY_LAST_SEEN, device.getLastSeenOnWifi());
storedDevice.put(ConnectableDevice.KEY_LAST_CONNECTED, device.getLastConnected());
storedDevice.put(ConnectableDevice.KEY_LAST_DETECTED, device.getLastDetection());
JSONObject services = storedDevice.optJSONObject(ConnectableDevice.KEY_SERVICES);
if (services == null)
services = new JSONObject();
for (DeviceService service : device.getServices()) {
JSONObject serviceInfo = service.toJSONObject();
if (serviceInfo != null)
services.put(service.getServiceDescription().getUUID(), serviceInfo);
}
storedDevice.put(ConnectableDevice.KEY_SERVICES, services);
storedDevices.put(device.getId(), storedDevice);
activeDevices.put(device.getId(), device);
store();
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void removeAll() {
activeDevices.clear();
storedDevices.clear();
store();
}
@Override
public JSONObject getStoredDevices() {
JSONObject ret = new JSONObject();
for (java.util.Map.Entry<String, JSONObject> entry: storedDevices.entrySet()) {
try {
ret.put(entry.getKey(), entry.getValue());
} catch (JSONException e) {
e.printStackTrace();
}
}
return ret;
}
@Override
public ConnectableDevice getDevice(String uuid) {
if (uuid == null || uuid.length() == 0)
return null;
ConnectableDevice foundDevice = getActiveDevice(uuid);
if (foundDevice == null) {
JSONObject foundDeviceInfo = getStoredDevice(uuid);
if (foundDeviceInfo != null)
foundDevice = new ConnectableDevice(foundDeviceInfo);
}
return foundDevice;
}
private ConnectableDevice getActiveDevice(String uuid) {
ConnectableDevice foundDevice = activeDevices.get(uuid);
if (foundDevice == null) {
for (ConnectableDevice device : activeDevices.values()) {
for (DeviceService service : device.getServices()) {
if (uuid.equals(service.getServiceDescription().getUUID())) {
return device;
}
}
}
}
return foundDevice;
}
private JSONObject getStoredDevice(String uuid) {
JSONObject foundDevice = storedDevices.get(uuid);
if (foundDevice == null) {
for (JSONObject device: storedDevices.values()) {
JSONObject services = device.optJSONObject(ConnectableDevice.KEY_SERVICES);
if (services != null && services.has(uuid))
return device;
}
}
return foundDevice;
}
@Override
public ServiceConfig getServiceConfig(ServiceDescription serviceDescription) {
if (serviceDescription == null) {
return null;
}
String uuid = serviceDescription.getUUID();
if (uuid == null || uuid.length() == 0) {
return null;
}
JSONObject device = getStoredDevice(uuid);
if (device != null) {
JSONObject services = device.optJSONObject(ConnectableDevice.KEY_SERVICES);
if (services != null) {
JSONObject service = services.optJSONObject(uuid);
if (service != null) {
JSONObject serviceConfigInfo = service.optJSONObject(DeviceService.KEY_CONFIG);
if (serviceConfigInfo != null) {
return ServiceConfig.getConfig(serviceConfigInfo);
}
}
}
}
return null;
}
// @cond INTERNAL
private void load() {
String line;
BufferedReader in = null;
File file = new File(fileFullPath);
if (!file.exists()) {
version = CURRENT_VERSION;
created = Util.getTime();
updated = Util.getTime();
} else {
boolean encounteredException = false;
try {
in = new BufferedReader(new FileReader(file));
StringBuilder sb = new StringBuilder();
while ((line = in.readLine()) != null) {
sb.append(line);
}
in.close();
JSONObject data = new JSONObject(sb.toString());
JSONArray deviceArray = data.optJSONArray(KEY_DEVICES);
if (deviceArray != null) {
for (int i = 0; i < deviceArray.length(); i++) {
JSONObject device = deviceArray.getJSONObject(i);
storedDevices.put(device.getString(ConnectableDevice.KEY_ID), device);
}
}
version = data.optInt(KEY_VERSION, CURRENT_VERSION);
created = data.optLong(KEY_CREATED, 0);
updated = data.optLong(KEY_UPDATED, 0);
} catch (IOException e) {
e.printStackTrace();
// it is likely that the device store has been corrupted
encounteredException = true;
} catch (JSONException e) {
e.printStackTrace();
// it is likely that the device store has been corrupted
encounteredException = true;
}
if (encounteredException && storedDevices == null) {
file.delete();
version = CURRENT_VERSION;
created = Util.getTime();
updated = Util.getTime();
}
}
}
private void store() {
updated = Util.getTime();
JSONObject deviceStore = new JSONObject();
try {
deviceStore.put(KEY_VERSION, version);
deviceStore.put(KEY_CREATED, created);
deviceStore.put(KEY_UPDATED, updated);
JSONArray deviceArray = new JSONArray(storedDevices.values());
deviceStore.put(KEY_DEVICES, deviceArray);
} catch (JSONException e) {
e.printStackTrace();
}
if (!waitToWrite)
writeStoreToDisk(deviceStore);
}
private synchronized void writeStoreToDisk(final JSONObject deviceStore) {
final long lastUpdate = updated;
waitToWrite = true;
Util.runInBackground(new Runnable() {
@Override
public void run() {
FileWriter out;
try {
File output = new File(fileFullPath);
if (!output.exists())
output.getParentFile().mkdirs();
out = new FileWriter(output);
out.write(deviceStore.toString());
out.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
waitToWrite = false;
}
if (lastUpdate < updated)
writeStoreToDisk(deviceStore);
}
});
}
// @endcond
}