/*
WebRTCVideoChatProfile.java
Copyright (c) 2015 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.webrtc.profile;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.webkit.URLUtil;
import org.deviceconnect.android.deviceplugin.webrtc.BuildConfig;
import org.deviceconnect.android.deviceplugin.webrtc.WebRTCApplication;
import org.deviceconnect.android.deviceplugin.webrtc.WebRTCDeviceService;
import org.deviceconnect.android.deviceplugin.webrtc.activity.VideoChatActivity;
import org.deviceconnect.android.deviceplugin.webrtc.core.Address;
import org.deviceconnect.android.deviceplugin.webrtc.core.Peer;
import org.deviceconnect.android.deviceplugin.webrtc.core.PeerConfig;
import org.deviceconnect.android.deviceplugin.webrtc.service.WebRTCService;
import org.deviceconnect.android.deviceplugin.webrtc.setting.SettingUtil;
import org.deviceconnect.android.deviceplugin.webrtc.util.WebRTCManager;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventError;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.message.DConnectMessageService;
import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.VideoChatProfile;
import org.deviceconnect.android.profile.api.DConnectApi;
import org.deviceconnect.android.profile.api.DeleteApi;
import org.deviceconnect.android.profile.api.GetApi;
import org.deviceconnect.android.profile.api.PostApi;
import org.deviceconnect.android.profile.api.PutApi;
import org.deviceconnect.message.DConnectMessage;
import java.util.ArrayList;
import java.util.List;
/**
* VideoChat Profile.
*
* @author NTT DOCOMO, INC.
*/
public class WebRTCVideoChatProfile extends VideoChatProfile {
/**
* Tag for debugging.
*/
private static final String TAG = "WEBRTC";
private final DConnectApi mGetProfileApi = new GetApi() {
@Override
public String getAttribute() {
return ATTR_PROFILE;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
String configParam = request.getStringExtra(PARAM_CONFIG);
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ onGetProfile");
Log.i(TAG, "config: " + configParam);
}
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer != null) {
String deviceName = SettingUtil.getDeviceName(getContext());
if (deviceName == null || deviceName.equals("")) {
deviceName = "WebRTC Plugin";
}
response.putExtra(PARAM_NAME, deviceName);
response.putExtra(PARAM_ADDRESSID, peer.getMyAddressId());
setResult(response, DConnectMessage.RESULT_OK);
} else {
MessageUtils.setInvalidRequestParameterError(response);
}
getWebRTCService().sendResponse(response);
}
});
return false;
}
};
private final DConnectApi mGetAddressApi = new GetApi() {
@Override
public String getAttribute() {
return ATTR_ADDRESS;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
final String configParam = request.getStringExtra(PARAM_CONFIG);
final String addressIdParam = request.getStringExtra(PARAM_ADDRESSID);
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ onGetAddress");
Log.i(TAG, "config: " + configParam);
}
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer != null) {
peer.getListPeerList(new Peer.OnGetAddressCallback() {
@Override
public void onGetAddresses(final List<Address> addressList) {
List<Bundle> list = new ArrayList<>();
for (int i = 0; i < addressList.size(); i++) {
Address a = addressList.get(i);
if (addressIdParam == null || addressIdParam.equals(a.getAddressId())) {
Bundle address = new Bundle();
address.putString(PARAM_NAME, a.getName());
address.putString(PARAM_ADDRESSID, a.getAddressId());
address.putString(PARAM_STATUS, a.getState().getValue());
list.add(address);
}
}
Bundle[] addresses = new Bundle[list.size()];
list.toArray(addresses);
response.putExtra(PARAM_ADDRESSES, addresses);
setResult(response, DConnectMessage.RESULT_OK);
getWebRTCService().sendResponse(response);
}
});
} else {
MessageUtils.setInvalidRequestParameterError(response);
getWebRTCService().sendResponse(response);
}
}
});
return false;
}
};
private final DConnectApi mPostCallApi = new PostApi() {
@Override
public String getAttribute() {
return ATTR_CALL;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
String configParam = request.getStringExtra(PARAM_CONFIG);
String groupIdParam = request.getStringExtra(PARAM_GROUPID);
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ onPostCall");
}
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
if (groupIdParam != null) {
MessageUtils.setInvalidRequestParameterError(response, "groupId is not supported.");
return true;
}
final WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer != null) {
peer.getListPeerList(new Peer.OnGetAddressCallback() {
@Override
public void onGetAddresses(final List<Address> addressList) {
String addressId = request.getStringExtra(PARAM_ADDRESSID);
String video = request.getStringExtra(PARAM_VIDEO);
String audio = request.getStringExtra(PARAM_AUDIO);
String outputs = request.getStringExtra(PARAM_OUTPUTS);
String audioSampleRate = request.getStringExtra(PARAM_AUDIOSAMPLERATE);
int audioSampleRateValue;
if (audioSampleRate == null || audioSampleRate.length() == 0) {
audioSampleRateValue = PARAM_RATE_48000;
audioSampleRate = String.valueOf(PARAM_RATE_48000);
} else {
try {
audioSampleRateValue = Integer.valueOf(audioSampleRate);
} catch(NumberFormatException e) {
// Characters that can not be converted to a number has been entered.
audioSampleRateValue = 0;
}
}
String audioBitDepth = request.getStringExtra(PARAM_AUDIOBITDEPTH);
if (audioBitDepth == null || audioBitDepth.length() == 0) {
audioBitDepth = PARAM_PCM_FLOAT;
}
String audioChannel = request.getStringExtra(PARAM_AUDIOCHANNEL);
if (audioChannel == null || audioChannel.length() == 0) {
audioChannel = PARAM_MONAURAL;
}
// if value is null, sets "true" as default
video = (video == null || video.isEmpty()) ? "true" : video;
audio = (audio == null || audio.isEmpty()) ? "true" : audio;
outputs = (outputs == null || outputs.isEmpty()) ? PARAM_HOST : outputs;
if (addressId == null || addressId.length() == 0) {
MessageUtils.setInvalidRequestParameterError(response, "addressId is invalid.");
} else if (!containAddressId(addressList, addressId)) {
MessageUtils.setInvalidRequestParameterError(response, "addressId is invalid.");
} else if (!checkUri(video)) {
MessageUtils.setInvalidRequestParameterError(response, "video is invalid.");
} else if (!checkUri(audio)) {
MessageUtils.setInvalidRequestParameterError(response, "audio is invalid.");
} else if (!checkAudioSampleRate(audioSampleRateValue)) {
MessageUtils.setInvalidRequestParameterError(response, "audioSampleRate is invalid.");
} else if (!checkAudioBitDepth(audioBitDepth)) {
MessageUtils.setInvalidRequestParameterError(response, "audioBitDepth is invalid.");
} else if (!checkAudioChannel(audioChannel)) {
MessageUtils.setInvalidRequestParameterError(response, "audioChannel is invalid.");
} else {
boolean offer = peer.hasOffer(addressId);
final Intent intent = new Intent();
intent.setClass(getContext(), VideoChatActivity.class);
intent.putExtra(VideoChatActivity.EXTRA_ADDRESS_ID, addressId);
intent.putExtra(VideoChatActivity.EXTRA_VIDEO_URI, video);
intent.putExtra(VideoChatActivity.EXTRA_AUDIO_URI, audio);
intent.putExtra(VideoChatActivity.EXTRA_CONFIG, peer.getConfig());
intent.putExtra(VideoChatActivity.EXTRA_OFFER, offer);
intent.putExtra(VideoChatActivity.EXTRA_AUDIOSAMPLERATE, audioSampleRate);
intent.putExtra(VideoChatActivity.EXTRA_AUDIOBITDEPTH, audioBitDepth);
intent.putExtra(VideoChatActivity.EXTRA_AUDIOCHANNEL, audioChannel);
intent.putExtra(VideoChatActivity.EXTRA_CALL_TIMESTAMP, System.currentTimeMillis());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (outputs.equals(PARAM_HOST)) {
getContext().startActivity(intent);
setResult(response, DConnectMessage.RESULT_OK);
} else {
WebRTCManager mgr = getWebRTCService().getWebRTCManager();
if (mgr.isConnect()) {
MessageUtils.setIllegalServerStateError(response, "Already the http server is running.");
} else {
mgr.connectOnUiThread(intent);
setResult(response, DConnectMessage.RESULT_OK);
}
}
}
getWebRTCService().sendResponse(response);
}
});
} else {
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
getWebRTCService().sendResponse(response);
}
}
});
return false;
}
};
private final DConnectApi mPutProfileApi = new PutApi() {
@Override
public String getAttribute() {
return ATTR_PROFILE;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
String name = request.getStringExtra(PARAM_NAME);
if (name == null || name.isEmpty()) {
MessageUtils.setInvalidRequestParameterError(response, "name is invalid.");
return true;
}
SettingUtil.setDeviceName(getContext(), name);
setResult(response, DConnectMessage.RESULT_OK);
return true;
}
};
private final DConnectApi mPutOnIncomingApi = new PutApi() {
@Override
public String getAttribute() {
return ATTR_ONINCOMING;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return registerEvent(request, response);
}
};
private final DConnectApi mPutOnCallApi = new PutApi() {
@Override
public String getAttribute() {
return ATTR_ONCALL;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return registerEvent(request, response);
}
};
private final DConnectApi mPutOnHangupApi = new PutApi() {
@Override
public String getAttribute() {
return ATTR_ONHANGUP;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return registerEvent(request, response);
}
};
private final DConnectApi mDeleteCallApi = new DeleteApi() {
@Override
public String getAttribute() {
return ATTR_CALL;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
String configParam = request.getStringExtra(PARAM_CONFIG);
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ onDeleteCall");
Log.i(TAG, "config:" + configParam);
}
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer != null) {
String addressId = request.getStringExtra(PARAM_ADDRESSID);
if (peer.hangup(addressId)) {
setResult(response, DConnectMessage.RESULT_OK);
} else {
MessageUtils.setInvalidRequestParameterError(response,
"address has not been call.");
}
} else {
MessageUtils.setInvalidRequestParameterError(response);
}
getWebRTCService().sendResponse(response);
}
});
return false;
}
};
private final DConnectApi mDeleteOnIncomingApi = new DeleteApi() {
@Override
public String getAttribute() {
return ATTR_ONINCOMING;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return unregisterEvent(request, response);
}
};
private final DConnectApi mDeleteOnCallApi = new DeleteApi() {
@Override
public String getAttribute() {
return ATTR_ONCALL;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return unregisterEvent(request, response);
}
};
private final DConnectApi mDeleteOnHangupApi = new DeleteApi() {
@Override
public String getAttribute() {
return ATTR_ONHANGUP;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
return unregisterEvent(request, response);
}
};
public WebRTCVideoChatProfile() {
addApi(mGetProfileApi);
addApi(mGetAddressApi);
addApi(mPostCallApi);
addApi(mPutProfileApi);
addApi(mPutOnIncomingApi);
addApi(mPutOnCallApi);
addApi(mPutOnHangupApi);
addApi(mDeleteCallApi);
addApi(mDeleteOnIncomingApi);
addApi(mDeleteOnCallApi);
addApi(mDeleteOnHangupApi);
}
/**
* Checks whether addressId is included in the addressList.
* @param addressList address list
* @param addressId address id
* @return true if addressId is included in the addressList, false otherwise
*/
private boolean containAddressId(final List<Address> addressList, final String addressId) {
if (addressList != null) {
for (Address address : addressList) {
if (addressId.equals(address.getAddressId())) {
return true;
}
}
}
return false;
}
/**
* Register the event.
* @param request request
* @param response response
* @return true if returns a response immediately, false otherwise
*/
private boolean registerEvent(final Intent request, final Intent response) {
String configParam = request.getStringExtra(PARAM_CONFIG);
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer == null) {
MessageUtils.setInvalidRequestParameterError(response, "peer not found.");
} else {
EventError error = EventManager.INSTANCE.addEvent(request);
switch (error) {
case NONE:
setResult(response, DConnectMessage.RESULT_OK);
peer.setPeerEventListener(mListener);
break;
case INVALID_PARAMETER:
MessageUtils.setInvalidRequestParameterError(response);
break;
default:
MessageUtils.setUnknownError(response);
break;
}
}
getWebRTCService().sendResponse(response);
}
});
return false;
}
/**
* Unregister the event.
* @param request request
* @param response response
* @return true if returns a response immediately, false otherwise
*/
private boolean unregisterEvent(final Intent request, final Intent response) {
String configParam = request.getStringExtra(PARAM_CONFIG);
PeerConfig config;
try {
config = new PeerConfig(configParam);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "", e);
}
MessageUtils.setInvalidRequestParameterError(response, "config is invalid.");
return true;
}
WebRTCApplication application = getWebRTCApplication();
application.getPeer(config, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer == null) {
MessageUtils.setInvalidRequestParameterError(response, "peer not found.");
} else {
EventError error = EventManager.INSTANCE.removeEvent(request);
switch (error) {
case NONE:
setResult(response, DConnectMessage.RESULT_OK);
break;
case INVALID_PARAMETER:
MessageUtils.setInvalidRequestParameterError(response);
break;
default:
MessageUtils.setUnknownError(response);
break;
}
}
getWebRTCService().sendResponse(response);
}
});
return false;
}
private WebRTCDeviceService getWebRTCService() {
return (WebRTCDeviceService) getContext();
}
/**
* Retrieve the instance of WebRTCApplication.
* @return WebRTCApplication
*/
private WebRTCApplication getWebRTCApplication() {
WebRTCDeviceService service = getWebRTCService();
return (WebRTCApplication) service.getApplication();
}
/**
* Returns whether this uri is valid.
* @param uri uri
* @return {@code true} if this uri is valid, {@code false} otherwise.
*/
private boolean checkUri(final String uri) {
if (uri == null) {
return false;
} else if ("true".equals(uri) || "false".equals(uri)) {
return true;
} else if (uri.startsWith("ws://") || uri.startsWith("wss://")) {
return true;
} else {
return URLUtil.isValidUrl(uri);
}
}
/**
* Check sample rate.
* @param sampleRate sampleRate.
* @return {@code true} if this samplerate is valid, {@code false} otherwise.
*/
private boolean checkAudioSampleRate(final int sampleRate) {
switch (sampleRate) {
case PARAM_RATE_22050:
case PARAM_RATE_32000:
case PARAM_RATE_44100:
case PARAM_RATE_48000:
return true;
default:
return false;
}
}
/**
* Check bit depth.
* @param bitDepth bitDepth.
* @return {@code true} if this bitdepth is valid, {@code false} otherwise.
*/
private boolean checkAudioBitDepth(final String bitDepth) {
if (bitDepth == null) {
// Parameters not set.
return true;
}
switch (bitDepth) {
case PARAM_PCM_8BIT:
case PARAM_PCM_16BIT:
case PARAM_PCM_FLOAT:
return true;
default:
return false;
}
}
/**
* Check channel.
* @param channel channel.
* @return {@code true} if this bitdepth is valid, {@code false} otherwise.
*/
private boolean checkAudioChannel(final String channel) {
if (channel == null) {
// Parameters not set.
return true;
}
switch (channel) {
case PARAM_MONAURAL:
case PARAM_STEREO:
return true;
default:
return false;
}
}
/**
* This listener that receive events from Peer.
*/
private Peer.PeerEventListener mListener = new Peer.PeerEventListener() {
@Override
public void onIncoming(final Peer peer, final Address address) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ run incoming event");
}
List<Event> events = EventManager.INSTANCE.getEventList(
WebRTCService.PLUGIN_ID,
PROFILE_NAME, null, ATTR_ONINCOMING);
if (events.size() != 0) {
Bundle arg = new Bundle();
arg.putString(PARAM_NAME, address.getName());
arg.putString(PARAM_ADDRESSID, address.getAddressId());
for (Event e : events) {
Intent event = EventManager.createEventMessage(e);
event.putExtra(PARAM_INCOMING, arg);
DConnectMessageService s = (DConnectMessageService) getContext();
s.sendEvent(event, e.getAccessToken());
}
}
}
@Override
public void onHangup(final Peer peer, final Address address) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ run onDisconnected event");
}
}
@Override
public void onCalling(final Peer peer, final Address address) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ run onCalling event");
}
}
};
}