/* * 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.google.android.apps.tvremote; 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 java.util.Timer; import java.util.TimerTask; import android.os.Handler; import android.os.Message; import android.util.Log; /** * An implementation of a trivial broadcast discovery protocol. * <p> * This client sends L3 broadcasts to probe for particular services on the * network. */ 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 = 9101; /** * Frequency of probe messages. */ private static final int PROBE_INTERVAL_MS = 6000; /** * Command name for a discovery request. */ private static final String COMMAND_DISCOVER = "discover"; /** * Service name to discover. */ private static final String DESIRED_SERVICE = "_anymote._tcp"; /** * Broadcast address of the local device. */ private final InetAddress mBroadcastAddress; /** * Timer to send probes. */ private final Timer mProbeTimer; /** * TimerTask to send probes. */ private final TimerTask mProbeTimerTask; /** * 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) { 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(); } mProbeTimer = new Timer(); mProbeTimerTask = new TimerTask() { @Override public void run() { BroadcastDiscoveryClient.this.sendProbe(); } }; 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[256]; mProbeTimer.schedule(mProbeTimerTask, 0, PROBE_INTERVAL_MS); while (true) { try { DatagramPacket packet = new DatagramPacket(buffer, buffer.length); mSocket.receive(packet); handleResponsePacket(packet); } catch (InterruptedIOException e) { // timeout } catch (IOException e) { // SocketException - stop() was called mProbeTimer.cancel(); break; } } Log.i(LOG_TAG, "Exiting client loop."); mProbeTimer.cancel(); } /** * Sends a single broadcast discovery request. */ private void sendProbe() { DatagramPacket packet = makeRequestPacket(DESIRED_SERVICE, mSocket.getLocalPort()); try { mSocket.send(packet); } catch (IOException 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 serviceName the service name to discover * @param responsePort the udp port number for replies * @return a new DatagramPacket */ private DatagramPacket makeRequestPacket(String serviceName, int responsePort) { String message = COMMAND_DISCOVER + " " + serviceName + " " + responsePort + "\n"; 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) { String strPacket = new String(packet.getData(), 0, packet.getLength()); String tokens[] = strPacket.trim().split("\\s+"); if (tokens.length != 3) { Log.w(LOG_TAG, "Malformed response: expected 3 tokens, got " + tokens.length); return; } BroadcastAdvertisement advert; try { String serviceType = tokens[0]; if (!serviceType.equals(DESIRED_SERVICE)) { return; } String serviceName = tokens[1]; int port = Integer.parseInt(tokens[2]); InetAddress addr = packet.getAddress(); Log.v(LOG_TAG, "Broadcast response: " + serviceName + ", " + addr + ", " + port); advert = new BroadcastAdvertisement(serviceName, addr, port); } catch (NumberFormatException e) { return; } Message message = mHandler.obtainMessage(DeviceFinder.BROADCAST_RESPONSE, advert); mHandler.sendMessage(message); } }