/*
ChromeCastCanvasProfile.java
Copyright (c) 2015 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.chromecast.profile;
import android.content.Intent;
import android.webkit.URLUtil;
import org.deviceconnect.android.deviceplugin.chromecast.ChromeCastService;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastDiscovery;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastHttpServer;
import org.deviceconnect.android.deviceplugin.chromecast.core.ChromeCastMessage;
import org.deviceconnect.android.deviceplugin.chromecast.core.MediaFile;
import org.deviceconnect.android.message.MessageUtils;
import org.deviceconnect.android.profile.CanvasProfile;
import org.deviceconnect.android.profile.api.DConnectApi;
import org.deviceconnect.android.profile.api.DeleteApi;
import org.deviceconnect.android.profile.api.PostApi;
import org.deviceconnect.message.DConnectMessage;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
/**
* Canvas Profile (Chromecast).
*
* @author NTT DOCOMO, INC.
*/
public class ChromeCastCanvasProfile extends CanvasProfile implements ChromeCastConstants {
/**
* Error message when Chromecast is not enabled.
*/
private static final String ERROR_MESSAGE_DEVICE_NOT_ENABLED = "Chromecast is not enabled.";
/**
* Prefix of image file name.
*/
private static final String PREFIX = "dConnectDeviceChromecast_";
/**
* The instance of {@link java.text.SimpleDateFormat}.
*/
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
/**
* Logger.
*/
private final Logger mLogger = Logger.getLogger("chromecast.dplugin");
public ChromeCastCanvasProfile() {
addApi(mPostDrawImageApi);
addApi(mDeleteDrawImageApi);
}
/**
* Generates an image file name.
*
* @return an image file name
*/
private static String generateFileName() {
Date timestamp = new Date(System.currentTimeMillis());
return PREFIX + FORMAT.format(timestamp);
}
private final DConnectApi mPostDrawImageApi = new PostApi() {
@Override
public String getAttribute() {
return CanvasProfile.ATTRIBUTE_DRAW_IMAGE;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
final String mimeType = CanvasProfile.getMIMEType(request);
final String serviceId = getServiceID(request);
final byte[] data = CanvasProfile.getData(request);
final String uri = CanvasProfile.getURI(request);
final double x = CanvasProfile.getX(request);
final double y = CanvasProfile.getY(request);
final String mode = CanvasProfile.getMode(request);
((ChromeCastService) getContext()).connectChromeCast(serviceId, new ChromeCastService.Callback() {
@Override
public void onResponse(final boolean connected) {
if (!connected) {
MessageUtils.setIllegalDeviceStateError(response, "The chromecast is not in local network.");
sendResponse(response);
return;
}
if (data == null && uri == null) {
MessageUtils.setInvalidRequestParameterError(response, "data is not specified.");
sendResponse(response);
return;
}
if (mimeType != null && !mimeType.contains("image")) {
MessageUtils.setInvalidRequestParameterError(response,
"Unsupported mimeType: " + mimeType);
sendResponse(response);
return;
}
if (uri != null && !URLUtil.isHttpsUrl(uri) && !URLUtil.isHttpUrl(uri)) {
MessageUtils.setInvalidRequestParameterError(response, "uri is not invalid.");
sendResponse(response);
return;
}
try {
String path;
if (data != null) {
path = exposeImage(data, mimeType);
} else {
path = uri;
}
mLogger.info("Exposed image: URL=" + path);
if (path == null) {
MessageUtils.setUnknownError(response, "The host device is not in local network.");
sendResponse(response);
return;
}
ChromeCastMessage app = ((ChromeCastService) getContext()).getChromeCastMessage();
if (!isDeviceEnable(response, app)) {
sendResponse(response);
return;
}
JSONObject json = new JSONObject();
json.put(KEY_FUNCTION, FUNCTION_POST_IMAGE);
json.put(KEY_URL, path);
json.put(KEY_MODE, mode);
json.put(KEY_X, x);
json.put(KEY_Y, y);
String message = json.toString();
mLogger.info("Send message successfully: " + message);
setResult(response, DConnectMessage.RESULT_OK);
app.sendMessage(response, message);
} catch (IOException e) {
MessageUtils.setUnknownError(response, "Failed to deploy image to Chromecast.");
sendResponse(response);
} catch (Exception e) {
MessageUtils.setUnknownError(response, e.getMessage());
sendResponse(response);
}
}
});
return false;
}
};
/**
* Expose an image.
*
* @param data the binary data of an image
* @param mimeType the mime type of an image
* @return the path of the exposed image
* @throws IOException if the specified image could not be exposed.
*/
private String exposeImage(final byte[] data, final String mimeType) throws IOException {
if (data == null) {
throw new IllegalArgumentException("data is null.");
}
File file = saveFile(generateFileName(), data);
return getHttpServer().exposeFile(new MediaFile(file, mimeType));
}
/**
* Save binary data on local file system.
*
* @param fileName filename
* @param data binary data
* @return stored file
* @throws IOException if the specified binary data could not be stored.
*/
private File saveFile(final String fileName, final byte[] data) throws IOException {
File dir = getContext().getFilesDir();
File file = new File(dir, generateFileName());
if (!file.exists()) {
if (!file.createNewFile()) {
throw new IOException("File is not be stored: " + fileName);
}
}
FileOutputStream fos = new FileOutputStream(file);
try {
fos.write(data);
fos.flush();
} finally {
fos.close();
}
return file;
}
/**
* Gets HTTP server to expose images.
*
* @return an instance of {@link ChromeCastHttpServer}
*/
private ChromeCastHttpServer getHttpServer() {
return ((ChromeCastService) getContext()).getChromeCastHttpServer();
}
private final DConnectApi mDeleteDrawImageApi = new DeleteApi() {
@Override
public String getAttribute() {
return CanvasProfile.ATTRIBUTE_DRAW_IMAGE;
}
@Override
public boolean onRequest(final Intent request, final Intent response) {
final String serviceId = getServiceID(request);
((ChromeCastService) getContext()).connectChromeCast(serviceId, new ChromeCastService.Callback() {
@Override
public void onResponse(final boolean connected) {
if (!connected) {
MessageUtils.setIllegalDeviceStateError(response, "The chromecast is not in local network.");
sendResponse(response);
return;
}
ChromeCastMessage app = ((ChromeCastService) getContext()).getChromeCastMessage();
if (!isDeviceEnable(response, app)) {
sendResponse(response);
return;
}
try {
JSONObject json = new JSONObject();
json.put(KEY_FUNCTION, FUNCTION_DELETE_IMAGE);
app.sendMessage(response, json.toString());
} catch (JSONException e) {
MessageUtils.setUnknownError(response);
sendResponse(response);
}
}
});
return false;
}
};
/**
* デバイスが有効か否かを返す<br/>.
* デバイスが無効の場合、レスポンスにエラーを設定する
*
* @param response レスポンス
* @param app ChromeCastMediaPlayer
* @return デバイスが有効か否か(有効: true, 無効: false)
*/
private boolean isDeviceEnable(final Intent response, final ChromeCastMessage app) {
if (!app.isDeviceEnable()) {
MessageUtils.setIllegalDeviceStateError(response, ERROR_MESSAGE_DEVICE_NOT_ENABLED);
setResult(response, DConnectMessage.RESULT_ERROR);
return false;
}
return true;
}
/**
* サービスからChromeCastDiscoveryrを取得する.
* @return ChromeCastDiscovery
*/
private ChromeCastDiscovery getChromeCastDiscovery() {
return ((ChromeCastService) getContext()).getChromeCastDiscovery();
}
}