Commit abfad7b0 authored by Steve Johnson's avatar Steve Johnson

Initial commit

parents
build/
app/build/
local.properties
app/src/main/java/com/cablelabs/android/dial/MSEConfig.java
This diff is collapsed.
#My Stuff Everywhere™ - mse-server-android repository
Android dial server application for My Stuff Everywhere.
##Configuration
You will need to copy MSEConfig.java from mse-site repository to app/src/main/java/com/cablelabs/android/dial and configure it for your external server.
See [https://html5.cablelabs.com/mse/quickstart.html](https://html5.cablelabs.com/mse/quickstart.html)
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="mse-server-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
</content>
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-19.0.0" level="project" />
<orderEntry type="library" exported="" name="mediarouter-v7-19.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
<orderEntry type="library" exported="" name="play-services-5.0.89" level="project" />
</component>
</module>
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion '20.0.0'
defaultConfig {
applicationId "com.cablelabs.android.dial"
minSdkVersion 19
targetSdkVersion 19
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
//compile files('libs/android-support-v4.jar')
//compile files('libs/libGoogleAnalyticsV2.jar')
//compile 'com.google.android.gms:play-services:+'
//compile 'com.android.support:mediarouter-v7:+'
compile 'com.android.support:support-v4:19.0.0'
compile 'com.android.support:appcompat-v7:19.0.0'
compile 'com.android.support:mediarouter-v7:19.0.0'
compile 'com.google.android.gms:play-services:5.0.89'
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cablelabs.android.dial"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="13"
android:targetSdkVersion="13" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<activity
android:name="com.cablelabs.android.dial.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.cablelabs.android.dial.SettingsActivity"
android:label="@string/app_name" >cp
<!--intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter-->
</activity>
</application>
</manifest>
\ No newline at end of file
/*
* Copyright (C) 2013 ENTERTAILION, LLC.
*
* 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.cablelabs.android.dial;
import java.net.InetAddress;
/*
HTTP/1.1 200 OK
USN: uuid:d17c2986-4624-3f2c-93a5-fe3ef0a0ec9c::urn:dial-multiscreen-org:service:dial:1
LOCATION: http://192.168.0.51:47944/dd.xml
BOOTID.UPNP.ORG: 1287126024
ST: urn:dial-multiscreen-org:service:dial:1
CACHE-CONTROL: max-age=1800
EXT
*/
public final class BroadcastAdvertisement {
private final String location;
private final InetAddress ipAddress;
private final int port;
BroadcastAdvertisement(String location, InetAddress ipAddress, int port) {
this.location = location;
this.ipAddress = ipAddress;
this.port = port;
}
public String getLocation() {
return location;
}
public InetAddress getIpAddress() {
return ipAddress;
}
public int getPort() {
return port;
}
}
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* 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.cablelabs.android.dial;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* DIAL protocol client:
* http://www.dial-multiscreen.org/dial-protocol-specification
*/
public class BroadcastDiscoveryClient implements Runnable {
private static final String LOG_TAG = "BroadcastDiscoveryClient";
/**
* UDP port to send probe messages to.
*/
private static final int BROADCAST_SERVER_PORT = 1900;
/**
* Frequency of probe messages.
*/
private static final int PROBE_INTERVAL_MS = 6000;
private static final int PROBE_INTERVAL_MS_MAX = 60000;
private static final String SEARCH_TARGET = "urn:dial-multiscreen-org:service:dial:1";
private static final String M_SEARCH = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 10\r\n" + "ST: "
+ SEARCH_TARGET + "\r\n\r\n";
private static final String HEADER_LOCATION = "LOCATION";
private static final String HEADER_ST = "ST";
private final InetAddress mBroadcastAddress;
private final Thread mBroadcastThread;
private int mBroadcastInterval;
private boolean mBroadcasting = true;
private boolean mReceiving = true;
/**
* Handle to main thread.
*/
private final Handler mHandler;
/**
* Send/receive socket.
*/
private final DatagramSocket mSocket;
/**
* Constructor
*
* @param broadcastAddress
* destination address for probes
* @param handler
* update Handler in main thread
*/
public BroadcastDiscoveryClient(InetAddress broadcastAddress, Handler handler) {
mReceiving = true;
mBroadcastAddress = broadcastAddress;
mHandler = handler;
try {
mSocket = new DatagramSocket(); // binds to random port
mSocket.setBroadcast(true);
} catch (SocketException e) {
Log.e(LOG_TAG, "Could not create broadcast client socket.", e);
throw new RuntimeException();
}
mBroadcastInterval = PROBE_INTERVAL_MS;
mBroadcastThread = new Thread(new Runnable() {
@Override
public void run() {
while (mBroadcasting) {
try {
BroadcastDiscoveryClient.this.sendProbe();
try {
Thread.sleep(mBroadcastInterval);
}
catch (InterruptedException e) {
}
/*
mBroadcastInterval = mBroadcastInterval * 2;
if (mBroadcastInterval > PROBE_INTERVAL_MS_MAX) {
mBroadcastInterval = PROBE_INTERVAL_MS_MAX;
mBroadcasting = false;
mReceiving = false;
}
*/
} catch (Throwable e) {
Log.e(LOG_TAG, "run", e);
}
}
}
});
Log.i(LOG_TAG, "Starting client on address " + mBroadcastAddress);
}
/** {@inheritDoc} */
public void run() {
Log.i(LOG_TAG, "Broadcast client thread starting.");
byte[] buffer = new byte[4096];
mBroadcastThread.start();
while (mReceiving) {
try {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
mSocket.receive(packet);
handleResponsePacket(packet);
} catch (InterruptedIOException e) {
// timeout
} catch (IOException e) {
// SocketException - stop() was called
break;
} catch (IllegalArgumentException e) {
break;
}
}
Log.i(LOG_TAG, "Exiting client loop.");
mBroadcasting = false;
mBroadcastThread.interrupt();
}
/**
* Sends a single broadcast discovery request.
*/
private void sendProbe() {
try {
DatagramPacket packet = makeRequestPacket(mSocket.getLocalPort());
mSocket.send(packet);
} catch (Throwable e) {
Log.e(LOG_TAG, "Exception sending broadcast probe", e);
return;
}
}
/**
* Immediately stops the receiver thread, and cancels the probe timer.
*/
public void stop() {
if (mSocket != null) {
mSocket.close();
}
}
/**
* Constructs a new probe packet.
*
* @param responsePort
* the udp port number for replies
* @return a new DatagramPacket
*/
private DatagramPacket makeRequestPacket(int responsePort) {
String message = M_SEARCH;
byte[] buf = message.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, mBroadcastAddress, BROADCAST_SERVER_PORT);
return packet;
}
/**
* Parse a received packet, and notify the main thread if valid.
*
* @param packet
* The locally-received DatagramPacket
*/
private void handleResponsePacket(DatagramPacket packet) {
try {
String strPacket = new String(packet.getData(), 0, packet.getLength());
//Log.d(LOG_TAG, "response=" + strPacket);
String tokens[] = strPacket.trim().split("\\n");
String location = null;
boolean foundSt = false;
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.startsWith(HEADER_LOCATION)) {
// LOCATION: http://192.168.0.51:47944/dd.xml
location = token.substring(10).trim();
} else if (token.startsWith(HEADER_ST)) {
// ST: urn:dial-multiscreen-org:service:dial:1
String st = token.substring(4).trim();
if (st.equals(SEARCH_TARGET)) {
foundSt = true;
}
}
}
if (!foundSt || location == null) {
Log.w(LOG_TAG, "Malformed response: " + strPacket);
return;
}
BroadcastAdvertisement advert;
try {
Uri uri = Uri.parse(location);
InetAddress address = InetAddress.getByName(uri.getHost());
advert = new BroadcastAdvertisement(location, address, uri.getPort());
} catch (Exception e) {
return;
}
Message message = mHandler.obtainMessage(DialDiscovery.BROADCAST_RESPONSE, advert);
mHandler.sendMessage(message);
} catch (Exception e) {
Log.e(LOG_TAG, "handleResponsePacket", e);
}
}
}
package com.cablelabs.android.dial;
/**
* Created by svenyonson on 8/22/14.
*/
public class ChromecastDevice {
private String deviceID;
private String friendlyName;
public ChromecastDevice(String id, String name) {
friendlyName = name;
deviceID = id;
}
public String getDeviceID() {
return deviceID;
}
public String getFriendlyName() {
return friendlyName;
}
}
package com.cablelabs.android.dial;
/**
* Created by svenyonson on 8/22/14.
*/
// TODO: apiClient, wire up text channel and launch logic.
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Message;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.util.Log;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class ChromecastDiscovery {
private static final String LOG_TAG = "ChromecastDiscovery";
private MediaRouter mediaRouter;
private MediaRouteSelector mediaRouteSelector;
private MediaRouter.Callback mediaRouterCallback;
private CastDevice selectedDevice;
private GoogleApiClient apiClient;
private Cast.Listener castListener;
private ConnectionCallbacks connectionCallbacks;
private ConnectionFailedListener connectionFailedListener;
private ChromecastTextChannel textChannel;
private static final String APP_ID = MSEConfig.MSE_CHROMECAST_APPID;
private boolean discoveryActive;
private MainActivity mainActivity;
private Context context;
private String sharedContainerUrl;
private Map<String, MediaRouter.RouteInfo> devicesByID;
public static final int DEVICE_LIST_UPDATE = 550;
ChromecastDiscovery(MainActivity main) {
mainActivity = main;
context = main.getApplicationContext();
mediaRouter = MediaRouter.getInstance(context);
mediaRouterCallback = new MediaRouterCallback();
devicesByID = new HashMap<String, MediaRouter.RouteInfo>();
textChannel = new ChromecastTextChannel();
castListener = new CastListener();
connectionCallbacks = new ConnectionCallbacks();
connectionFailedListener = new ConnectionFailedListener();
}
public void startDiscovery() {
mediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(APP_ID))
.build();
mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
discoveryActive = true;
}
public void stopDiscovery() {
mediaRouter.removeCallback(mediaRouterCallback);
mediaRouteSelector = null;
discoveryActive = false;
}
public boolean isChromecast(String deviceID) {
return devicesByID.get(deviceID) != null;
}
public int launchApplication(String deviceID, String url) {
final MediaRouter.RouteInfo route = devicesByID.get(deviceID);
sharedContainerUrl = url;
// Connect to Chromecast
mainActivity.runOnUiThread(new Runnable() {
public void run() {
selectedDevice = CastDevice.getFromBundle(route.getExtras());
mediaRouter.selectRoute(route);
}
});
return 0;
}
public ChromecastDevice[] getDeviceList() {
List<ChromecastDevice> deviceList = new ArrayList<ChromecastDevice>();
ChromecastDevice[] devices = new ChromecastDevice[0];
Iterator it = devicesByID.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
MediaRouter.RouteInfo route = (MediaRouter.RouteInfo)entry.getValue();
ChromecastDevice device = new ChromecastDevice(route.getId(), route.getName());
deviceList.add(device);
}
return deviceList.toArray(new ChromecastDevice[0]);
}
private void connectApi() {
Cast.CastOptions apiOptions = Cast.CastOptions.builder(selectedDevice, castListener)
.build();
apiClient = new GoogleApiClient.Builder(mainActivity)
.addApi(Cast.API, apiOptions)
.addConnectionCallbacks(connectionCallbacks)
.addOnConnectionFailedListener(connectionFailedListener)
.build();
apiClient.connect();
}
private class MediaRouterCallback extends MediaRouter.Callback {
@Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
Log.d(LOG_TAG, "onRouteSelected: " + route);
//GameActivity.this.onRouteSelected(route);
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mainActivity.getApplicationContext());
if (resultCode == ConnectionResult.SUCCESS) {
connectApi();
} else if (resultCode == ConnectionResult.SERVICE_MISSING ||
resultCode == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED ||
resultCode == ConnectionResult.SERVICE_DISABLED) {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, mainActivity, 1);
dialog.show();
}
}
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
Log.d(LOG_TAG, "onRouteUnselected: " + route);
//GameActivity.this.onRouteUnselected(route);
}
@Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
Log.d(LOG_TAG, "onRouteAdded: " + route);
//GameActivity.this.onRouteUnselected(route);
devicesByID.put(route.getId(), route);
Message msg = Message.obtain();
msg.what = DEVICE_LIST_UPDATE;
msg.obj = null;
mainActivity._handler.sendMessage(msg);
}