Work item 13 - Onboarding multiple devices in a batch

parent 99338e72
......@@ -76,8 +76,9 @@ public class DoxsRepository {
} else if (oxm == OcfOxmType.OC_OXMTYPE_RDP) {
ret = OCObt.requestRandomPin(uuid, (OCUuid ocUuid, int status) -> {
if (status >= 0) {
Timber.d("Successfully request Random PIN " + OCUuidUtil.uuidToString(ocUuid));
String pin = randomPinHandler.handler();
String id = OCUuidUtil.uuidToString(ocUuid);
Timber.d("Successfully request Random PIN " + id);
String pin = randomPinHandler.handler(id);
if (OCObt.performRandomPinOtm(uuid, pin, handler) != -1){
emitter.onComplete();
} else {
......
......@@ -602,6 +602,38 @@ public class IotivityRepository {
});
}
public Completable post(String host, String uri, String deviceId, Map<String, Object> values) {
return Completable.create(emitter -> {
OCEndpoint ep = OCEndpointUtil.stringToEndpoint(host, new String[1]);
OCUuid uuid = OCUuidUtil.stringToUuid(deviceId);
OCEndpointUtil.setDi(ep, uuid);
OCResponseHandler handler = (OCClientResponse response) -> {
OCStatus code = response.getCode();
if (code == OCStatus.OC_STATUS_OK
|| code == OCStatus.OC_STATUS_CHANGED) {
emitter.onComplete();
} else {
emitter.onError(new Exception("POST " + uri + " error - code: " + code));
}
};
if (OCMain.initPost(uri, ep, null, handler, OCQos.HIGH_QOS)) {
CborEncoder root = OCRep.beginRootObject();
parseOCRepresentionToCbor(root, values);
OCRep.endRootObject();
if (!OCMain.doPost()) {
emitter.onError(new Exception("Do POST " + uri + " error"));
}
} else {
emitter.onError(new Exception("Init POST " + uri + " error"));
}
OCEndpointUtil.freeEndpoint(ep);
});
}
private void parseOCRepresentionToCbor(CborEncoder parent, OCRepresentation rep, Object valueArray) {
while (rep != null) {
switch (rep.getType()) {
......@@ -626,6 +658,9 @@ public class IotivityRepository {
case OC_REP_STRING_ARRAY:
OCRep.setStringArray(parent, rep.getName(), (String[])valueArray);
break;
case OC_REP_BOOL_ARRAY:
OCRep.setBooleanArray(parent, rep.getName(), (boolean[])valueArray);
break;
default:
break;
}
......@@ -634,6 +669,46 @@ public class IotivityRepository {
}
}
private void parseOCRepresentionToCbor(CborEncoder parent, Map<String, Object> values) {
for (String key : values.keySet()) {
if (values.get(key) instanceof Boolean) {
OCRep.setBoolean(parent, key, (boolean)values.get(key));
} else if (values.get(key) instanceof Integer) {
OCRep.setLong(parent, key, (Integer)values.get(key));
} else if (values.get(key) instanceof Double) {
OCRep.setDouble(parent, key, (Double)values.get(key));
} else if (values.get(key) instanceof String) {
OCRep.setTextString(parent, key, (String)values.get(key));
} else if (values.get(key) instanceof List) {
if (((List) values.get(key)).get(0) instanceof String) {
String[] ret = new String[((List<String>)values.get(key)).size()];
for (int i=0; i< ((List<String>)values.get(key)).size(); i++) {
ret[i] = ((List<String>)values.get(key)).get(i);
}
OCRep.setStringArray(parent, key, ret);
} else if (((List) values.get(key)).get(0) instanceof Integer) {
long[] ret = new long[((List<Integer>)values.get(key)).size()];
for (int i=0; i< ((List<Integer>)values.get(key)).size(); i++) {
ret[i] = ((List<Integer>)values.get(key)).get(i);
}
OCRep.setLongArray(parent, key, ret);
} else if (((List) values.get(key)).get(0) instanceof Double) {
double[] ret = new double[((List<Double>)values.get(key)).size()];
for (int i=0; i< ((List<Double>)values.get(key)).size(); i++) {
ret[i] = ((List<Double>)values.get(key)).get(i);
}
OCRep.setDoubleArray(parent, key, ret);
} else if (((List) values.get(key)).get(0) instanceof Boolean) {
boolean[] ret = new boolean[((List<Boolean>)values.get(key)).size()];
for (int i=0; i< ((List<Boolean>)values.get(key)).size(); i++) {
ret[i] = ((List<Boolean>)values.get(key)).get(i);
}
OCRep.setBooleanArray(parent, key, ret);
}
}
}
}
public void close() {
Timber.d("Calling OCMain.mainShutdown()");
OCMain.mainShutdown();
......
package org.openconnectivity.otgc.domain.usecase;
import org.openconnectivity.otgc.data.repository.DoxsRepository;
import org.openconnectivity.otgc.data.repository.IotivityRepository;
import org.openconnectivity.otgc.data.repository.PreferencesRepository;
import org.openconnectivity.otgc.domain.model.devicelist.Device;
import org.openconnectivity.otgc.utils.constant.OcfOxmType;
import org.openconnectivity.otgc.utils.rx.SchedulersFacade;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import io.reactivex.Single;
public class OnboardDevicesUseCase {
/* Repositories */
private final IotivityRepository iotivityRepository;
private final DoxsRepository doxsRepository;
private final PreferencesRepository settingRepository;
/* Scheduler */
private final SchedulersFacade schedulersFacade;
@Inject
public OnboardDevicesUseCase(IotivityRepository iotivityRepository,
DoxsRepository doxsRepository,
PreferencesRepository settingRepository,
SchedulersFacade schedulersFacade) {
this.iotivityRepository = iotivityRepository;
this.doxsRepository = doxsRepository;
this.settingRepository = settingRepository;
this.schedulersFacade = schedulersFacade;
}
public Single<Device> execute(Device device, List<OcfOxmType> oxms) {
int it1 = oxms.size() - 1;
if (it1 < 0) {
return null;
}
return executeOnboard(device, oxms.get(it1))
.onErrorResumeNext(error1 -> {
int it2 = it1 - 1;
if (it2 < 0) {
return null;
}
return iotivityRepository.scanUnownedDevices()
.filter(device1 -> device.getDeviceId().equals(device1.getDeviceId()))
.firstOrError()
.flatMap(device1 ->
executeOnboard(device1, oxms.get(it2))
.onErrorResumeNext(error -> {
int it3 = it1 - 2;
if (it3 < 0) {
return null;
}
return iotivityRepository.scanUnownedDevices()
.filter(device2 -> device.getDeviceId().equals(device2.getDeviceId()))
.firstOrError()
.flatMap(device2 -> executeOnboard(device2, oxms.get(it3)));
})
);
});
}
private Single<Device> executeOnboard(Device deviceToOnboard, OcfOxmType oxm) {
int delay = settingRepository.getRequestsDelay();
final Single<Device> getUpdatedOcSecureResource = iotivityRepository.scanOwnedDevices()
.filter(device -> deviceToOnboard.getDeviceId().equals(device.getDeviceId())
|| deviceToOnboard.equalsHosts(device))
.singleOrError();
return doxsRepository.doOwnershipTransfer(deviceToOnboard.getDeviceId(), oxm)
.delay(2 * delay, TimeUnit.SECONDS, schedulersFacade.ui())
.andThen(getUpdatedOcSecureResource
.onErrorResumeNext(error -> getUpdatedOcSecureResource
.retry(2)
.onErrorResumeNext(Single.error(error)))
);
}
}
......@@ -23,5 +23,5 @@
package org.openconnectivity.otgc.utils.handler;
public interface OCSetRandomPinHandler {
public String handler();
public String handler(String uuid);
}
......@@ -37,6 +37,7 @@ import org.openconnectivity.otgc.utils.rx.SchedulersFacade;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.selection.SelectionTracker;
......@@ -67,63 +68,106 @@ public class ActionModeController implements ActionMode.Callback {
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
MenuItem linkMenuItem = menu.findItem(R.id.action_pairwise);
MenuItem unlinkMenuItem = menu.findItem(R.id.action_unlink);
if (mSelectionTracker.hasSelection() && mSelectionTracker.getSelection().size() == 2) {
Device server = null;
Device client = null;
Iterator<Device> deviceIterable = mSelectionTracker.getSelection().iterator();
while (deviceIterable.hasNext()) {
Device device = deviceIterable.next();
if (device.getDeviceRole().equals(DeviceRole.SERVER)
&& device.getDeviceType().equals(DeviceType.OWNED_BY_SELF)) {
server = device;
} else if (device.getDeviceRole().equals(DeviceRole.CLIENT)
&& device.getDeviceType().equals(DeviceType.OWNED_BY_SELF)) {
client = device;
MenuItem onboardMenuItem = menu.findItem(R.id.action_onboard);
if (mSelectionTracker.hasSelection()) {
boolean areUnowned = true;
Iterator<Device> itDevice = mSelectionTracker.getSelection().iterator();
while (itDevice.hasNext()) {
Device d = itDevice.next();
if (d.getDeviceType() != DeviceType.UNOWNED) {
areUnowned = false;
break;
}
}
if (server != null && client != null) {
String serverId = server.getDeviceId();
final Device c = client;
final Device s = server;
// Create dialog
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(R.string.dialog_wait_title);
builder.setMessage(R.string.dialog_wait_message);
AlertDialog waitDialog = builder.create();
new CompositeDisposable().add(retrieveLinkedDevicesUseCase.execute(c)
.onErrorReturnItem(new ArrayList<>())
.subscribeOn(new SchedulersFacade().io())
.observeOn(new SchedulersFacade().ui())
.doOnSubscribe(__ -> waitDialog.show())
.doFinally(() -> waitDialog.dismiss())
.subscribe(
linkClientDevices -> {
if (serverId != null) {
if (linkClientDevices.contains(serverId)) {
unlinkMenuItem.setOnMenuItemClickListener(menuItem -> {
actionMode.finish();
return sMyMenuItemClickListener.onMenuItemClick(menuItem, c, s);
});
unlinkMenuItem.setVisible(true);
} else {
linkMenuItem.setOnMenuItemClickListener(menuItem -> {
actionMode.finish();
return sMyMenuItemClickListener.onMenuItemClick(menuItem, c, s);
});
linkMenuItem.setVisible(true);
}
}
},
throwable -> Toast.makeText(mContext, R.string.devices_link_error, Toast.LENGTH_SHORT)
));
if (!areUnowned && mSelectionTracker.getSelection().size() == 2) {
Device server = null;
Device client = null;
Iterator<Device> deviceIterable = mSelectionTracker.getSelection().iterator();
while (deviceIterable.hasNext()) {
Device device = deviceIterable.next();
if (device.getDeviceRole().equals(DeviceRole.SERVER)
&& device.getDeviceType().equals(DeviceType.OWNED_BY_SELF)) {
server = device;
} else if (device.getDeviceRole().equals(DeviceRole.CLIENT)
&& device.getDeviceType().equals(DeviceType.OWNED_BY_SELF)) {
client = device;
}
}
if (server != null && client != null) {
String serverId = server.getDeviceId();
final Device c = client;
final Device s = server;
// Create dialog
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(R.string.dialog_wait_title);
builder.setMessage(R.string.dialog_wait_message);
AlertDialog waitDialog = builder.create();
new CompositeDisposable().add(retrieveLinkedDevicesUseCase.execute(c)
.onErrorReturnItem(new ArrayList<>())
.subscribeOn(new SchedulersFacade().io())
.observeOn(new SchedulersFacade().ui())
.doOnSubscribe(__ -> waitDialog.show())
.doFinally(() -> waitDialog.dismiss())
.subscribe(
linkClientDevices -> {
if (serverId != null) {
if (linkClientDevices.contains(serverId)) {
unlinkMenuItem.setOnMenuItemClickListener(menuItem -> {
actionMode.finish();
return sMyMenuItemClickListener.onMenuItemClick(menuItem, c, s);
});
unlinkMenuItem.setVisible(true);
linkMenuItem.setVisible(false);
onboardMenuItem.setVisible(false);
} else {
linkMenuItem.setOnMenuItemClickListener(menuItem -> {
actionMode.finish();
return sMyMenuItemClickListener.onMenuItemClick(menuItem, c, s);
});
linkMenuItem.setVisible(true);
unlinkMenuItem.setVisible(false);
onboardMenuItem.setVisible(false);
}
}
},
throwable -> Toast.makeText(mContext, R.string.devices_link_error, Toast.LENGTH_SHORT)
));
} else {
linkMenuItem.setVisible(false);
unlinkMenuItem.setVisible(false);
onboardMenuItem.setVisible(false);
}
} else if (areUnowned) {
onboardMenuItem.setOnMenuItemClickListener(menuItem -> {
List<Device> devices = new ArrayList<>();
Iterator<Device> deviceIterator = mSelectionTracker.getSelection().iterator();
while (deviceIterator.hasNext()) {
devices.add(deviceIterator.next());
}
actionMode.finish();
return sMyMenuItemClickListener.onMenuItemClick(menuItem, devices);
});
onboardMenuItem.setVisible(true);
linkMenuItem.setVisible(false);
unlinkMenuItem.setVisible(false);
} else {
linkMenuItem.setVisible(false);
unlinkMenuItem.setVisible(false);
onboardMenuItem.setVisible(false);
}
} else {
linkMenuItem.setVisible(false);
unlinkMenuItem.setVisible(false);
onboardMenuItem.setVisible(false);
}
return true;
}
......@@ -144,5 +188,6 @@ public class ActionModeController implements ActionMode.Callback {
public interface MyMenuItemClickListener {
boolean onMenuItemClick(MenuItem menuItem, Device client, Device server);
boolean onMenuItemClick(MenuItem menuItem, List<Device> devices);
}
}
......@@ -84,12 +84,13 @@ public class DeviceListActivity extends AppCompatActivity implements HasSupportF
String verifyPin = "";
OCSetRandomPinHandler randomPinCallbackListener = () -> {
OCSetRandomPinHandler randomPinCallbackListener = (String uuid) -> {
Timber.d("Inside randomPinListener");
final Object lock = new Object();
runOnUiThread(() -> {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(new ContextThemeWrapper(DeviceListActivity.this, R.style.AppTheme));
alertDialog.setTitle(DeviceListActivity.this.getString(R.string.devices_dialog_insert_randompin_title));
alertDialog.setMessage(uuid + ": ");
final EditText input = new EditText(DeviceListActivity.this);
alertDialog.setView(input);
alertDialog.setCancelable(false);
......
......@@ -41,6 +41,7 @@ import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
......@@ -162,15 +163,30 @@ public class DoxsFragment extends Fragment implements DoxsViewModel.SelectOxMLis
if (mActionMode == null) {
ActionModeController actionController =
new ActionModeController(getActivity(), mSelectionTracker, retrieveLinkedDevicesUseCase);
ActionModeController.setOnMenuItemClickListener((menuItem, client, server) -> {
if (menuItem.getItemId() == R.id.action_pairwise) {
mViewModel.pairwiseDevices(client, server);
} else if (menuItem.getItemId() == R.id.action_unlink) {
mViewModel.unlinkDevices(client, server);
ActionModeController.MyMenuItemClickListener menuListener = new ActionModeController.MyMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem, Device client, Device server) {
if (menuItem.getItemId() == R.id.action_pairwise) {
mViewModel.pairwiseDevices(client, server);
} else if (menuItem.getItemId() == R.id.action_unlink) {
mViewModel.unlinkDevices(client, server);
}
return true;
}
@Override
public boolean onMenuItemClick(MenuItem menuItem, List<Device> devices) {
if (menuItem.getItemId() == R.id.action_onboard) {
mViewModel.onboardAllDevices(devices);
}
return true;
}
};
return true;
});
ActionModeController.setOnMenuItemClickListener(menuListener);
mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionController);
} else {
mActionMode.invalidate();
......@@ -274,6 +290,12 @@ public class DoxsFragment extends Fragment implements DoxsViewModel.SelectOxMLis
mViewModel.getOffboardResponse().observe(this, this::processOffboardResponse);
mViewModel.getUpdatedDevice().observe(this, this::processUpdateDevice);
mViewModel.getOnboardWaiting().observe(this, this::processOnboardWaiting);
mViewModel.getOtmMultiResponse().observe(this, this::processOtmMultiResponse);
mViewModel.getDeviceInfoMultiResponse().observe(this, this::processDeviceInfoMultiResponse);
mViewModel.getDeviceRoleMultiResponse().observe(this, this::processDeviceRoleMultiResponse);
mViewModel.provisionAceOtmMultiResponse().observe(this, this::processProvisionAceOtmMultiResponse);
mViewModel.getConnectWifiEasySetupResponse().observe(this, this::processConnectWifiEasySetupResponse);
if (getActivity() != null) {
......@@ -509,6 +531,86 @@ public class DoxsFragment extends Fragment implements DoxsViewModel.SelectOxMLis
}
}
private void processOnboardWaiting(Response<Boolean> response) {
switch (response.status) {
case SUCCESS:
if (response.data) {
if (adPersonal == null) {
adPersonal = UiUtils.createProgressDialog(getActivity(), getString(R.string.devices_dialog_onboarding_otm_message));
} else if (!adPersonal.isShowing()) {
adPersonal.show();
}
} else {
if (adPersonal != null && adPersonal.isShowing()) {
adPersonal.dismiss();
adPersonal = null;
onSwipeRefresh();
}
}
break;
default:
break;
}
}
private void processOtmMultiResponse(Response<Device> response) {
switch (response.status) {
case LOADING:
break;
case SUCCESS:
break;
case ERROR:
Toast.makeText(getActivity(), R.string.devices_error_transferring_ownership, Toast.LENGTH_SHORT).show();
break;
}
}
private void processDeviceInfoMultiResponse(Response<Device> response) {
switch (response.status) {
case LOADING:
break;
case SUCCESS:
break;
case ERROR:
Toast.makeText(getActivity(), R.string.devices_error_device_info, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
private void processDeviceRoleMultiResponse(Response<Device> response) {
switch (response.status) {
case LOADING:
break;
case SUCCESS:
/*if (response.data != null) {
positionBeingUpdated = mAdapter.updateItem(positionBeingUpdated, response.data);
positionBeingUpdated = 0;
}*/
break;
case ERROR:
Toast.makeText(getActivity(), R.string.devices_error_device_role, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
private void processProvisionAceOtmMultiResponse(Response<Device> response) {
switch (response.status) {
case LOADING:
break;
case SUCCESS:
break;
case ERROR:
Toast.makeText(getActivity(), R.string.devices_error_provision_ace_otm, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
private void buildConnectWifiDialog(List<WifiNetwork> wifiNetworks) {
if (mConnectWifiDialog == null) {
ArrayList<WifiNetwork> wifiArrayList = new ArrayList<>();
......
......@@ -28,6 +28,7 @@ import androidx.lifecycle.MutableLiveData;
import org.openconnectivity.otgc.domain.usecase.GetDeviceDatabaseUseCase;
import org.openconnectivity.otgc.domain.usecase.GetDeviceIdUseCase;
import org.openconnectivity.otgc.domain.usecase.GetModeUseCase;
import org.openconnectivity.otgc.domain.usecase.OnboardDevicesUseCase;
import org.openconnectivity.otgc.domain.usecase.accesscontrol.CreateAclUseCase;
import org.openconnectivity.otgc.domain.usecase.wifi.CheckConnectionUseCase;
import org.openconnectivity.otgc.domain.usecase.GetDeviceInfoUseCase;
......@@ -68,6 +69,7 @@ public class DoxsViewModel extends BaseViewModel {
private final ScanDevicesUseCase mScanDevicesUseCase;
private final GetOTMethodsUseCase mGetOTMethodsUseCase;
private final OnboardUseCase mOnboardUseCase;
private final OnboardDevicesUseCase mOnboardDevicesUseCase;
private final CreateAclUseCase mCreateAclUseCase;
private final OffboardUseCase mOffboardUseCase;
private final GetDeviceInfoUseCase mGetDeviceInfoUseCase;
......@@ -94,6 +96,13 @@ public class DoxsViewModel extends BaseViewModel {
private final MutableLiveData<Response<List<WifiNetwork>>> scanResponse = new MutableLiveData<>();
private final MutableLiveData<Response<Void>> connectWifiEasySetupResponse = new MutableLiveData<>();
// Onboard selected devices
private final MutableLiveData<Response<Boolean>> onboardWaiting = new MutableLiveData<>();
private final MutableLiveData<Response<Device>> otmMultiResponse = new MutableLiveData<>();
private final MutableLiveData<Response<Device>> deviceInfoMultiResponse = new MutableLiveData<>();
private final MutableLiveData<Response<Device>> deviceRoleMultiResponse = new MutableLiveData<>();
private final MutableLiveData<Response<Device>> provisionAceOtmMultiResponse = new MutableLiveData<>();
private SelectOxMListener mOxmListener;
@Inject
......@@ -103,6 +112,7 @@ public class DoxsViewModel extends BaseViewModel {
ScanDevicesUseCase scanDevicesUseCase,
GetOTMethodsUseCase getOTMethodsUseCase,
OnboardUseCase onboardUseCase,
OnboardDevicesUseCase onboardDevicesUseCase,
CreateAclUseCase createAclUseCase,
OffboardUseCase offboardUseCase,
GetDeviceInfoUseCase getDeviceInfoUseCase,
......@@ -121,6 +131,7 @@ public class DoxsViewModel extends BaseViewModel {
this.mScanDevicesUseCase = scanDevicesUseCase;
this.mGetOTMethodsUseCase = getOTMethodsUseCase;
this.mOnboardUseCase = onboardUseCase;
this.mOnboardDevicesUseCase = onboardDevicesUseCase;
this.mCreateAclUseCase = createAclUseCase;
this.mOffboardUseCase = offboardUseCase;
this.mGetDeviceInfoUseCase = getDeviceInfoUseCase;
......@@ -185,6 +196,26 @@ public class DoxsViewModel extends BaseViewModel {
return connectWifiEasySetupResponse;