/* * Copyright (C) 2012 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.example.google.tv.anymotelibrary.connection; import android.app.Activity; import android.net.DhcpInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Message; import android.util.Log; import com.example.google.tv.anymotelibrary.R; import com.example.google.tv.anymotelibrary.client.AnymoteClientService; import com.example.google.tv.anymotelibrary.connection.BroadcastDiscoveryClient.BroadcastAdvertisement; import com.example.google.tv.anymotelibrary.connection.BroadcastDiscoveryClient.DeviceDiscoveredListener; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; /** * Service which discovers Google TV devices on the local network. */ public class TvDiscoveryService extends Handler { /** * Tag for debug logging. */ private static final String LOG_TAG = "TvDiscoveryService"; /** * Anymote service name. */ private static final String SERVICE_TCP = "_anymote._tcp"; /** * The service that handles connection to the TV device and sends events to * it. */ private final AnymoteClientService coreService; /** * The Broadcast client that listens for L3 broadcasts for Anymote service * on the network. */ private BroadcastDiscoveryClient broadcastClient; /** * The thread that handles network communications. */ private Thread broadcastThread; /** * The wifi connectivity manager. */ WifiManager wifiManager; /** * All discovered TVs are stored in this list. */ private List<TvDevice> devices; /** * Constructor * * @param coreService The service that handles connectivity to the TV * device. */ public TvDiscoveryService(AnymoteClientService coreService) { this.coreService = coreService; devices = new ArrayList(); wifiManager = (WifiManager) coreService.getSystemService(Activity.WIFI_SERVICE); } /** * Enum that declares internal messages. */ private enum RequestType { BROADCAST_TIMEOUT, } /** * Sends message to the handler. * * @param type */ private void sendMessage(RequestType type) { sendMessage(type, null, 0); } /** * Lock object to synchronize threads. */ Object broadCastSync = new Object(); /** * Send messages to the handler with a delay. * * @param type * @param obj * @param timeout */ private void sendMessage(RequestType type, Object obj, long timeout) { Message message = obtainMessage(type.ordinal(), obj); if (timeout != 0) { super.sendMessageDelayed(message, timeout); } else { super.sendMessage(message); } } /** * The looper for thread that discovers Google TV devices offering Anymote * service on the local network. */ DiscoveryLooper looper = new DiscoveryLooper(); /** * Returns a list of Google TV devices offering Anymote service on the local * network. * * @return list of TV devices */ public List<TvDevice> discoverTvs() { looper.start(); try { synchronized (broadCastSync) { broadCastSync.wait(); } } catch (InterruptedException e) { Log.e(LOG_TAG, "Interrupted while scanning for tvs"); // Return empty array list as this.devices might be unsafe // because of background looper, which may still be writing to it. return new ArrayList<TvDevice>(); } return devices; } /** * Called when network scan for discovering Google TV devices is completed. */ public void onDeviceScanComplete() { stopBroadcast(); } /** * Called when a Google TV device is found on local network. * * @param dev */ public void onDeviceFound(TvDevice dev) { devices.add(dev); } /** * Stops looking for Google TV devices on the network. */ private synchronized void stopBroadcast() { if (broadcastClient != null) { Log.i(LOG_TAG, "Disabling broadcast"); broadcastClient.stop(); broadcastClient = null; try { broadcastThread.join(1000); } catch (InterruptedException e) { Log.i(LOG_TAG, "Timeout while waiting for thread execution to complete"); } broadcastThread = null; onDeviceScanComplete(); } } /** * Starts scanning the local network for Google TV devices. */ private synchronized void startBroadcast() { Inet4Address broadcastAddress = getBroadcastAddress(); if (broadcastAddress == null) { stopBroadcast(); return; } if (broadcastClient == null) { Log.i(LOG_TAG, "Enabling broadcast"); broadcastClient = new BroadcastDiscoveryClient(broadcastAddress, getServiceName()); broadcastClient.setDeviceDiscoveredListener(new DeviceDiscoveredListener() { public void onDeviceDiscovered(BroadcastAdvertisement advert) { TvDevice remoteDevice = getDeviceFromAdvert(advert); Log.i(LOG_TAG, "Found wireless device: " + remoteDevice.getName()); onDeviceFound(remoteDevice); } }); broadcastThread = new Thread(broadcastClient); broadcastThread.start(); int broadcastTimeout = coreService.getResources().getInteger( R.integer.broadcast_timeout); sendMessage(RequestType.BROADCAST_TIMEOUT, null, broadcastTimeout); } } /** * Internal Looper thread that does the discovery */ private class DiscoveryLooper extends Thread { public Handler mHandler; @Override public void run() { startBroadcast(); if (getBroadcastAddress() == null) { devices = null; return; } } } public void handleMessage(Message msg) { RequestType request = RequestType.values()[msg.what]; if (request == RequestType.BROADCAST_TIMEOUT) { stopBroadcast(); synchronized (broadCastSync) { broadCastSync.notifyAll(); } } } /** * Extracts Device defination from network broadcast. * * @param adv network broadcast * @return TV device instance */ protected TvDevice getDeviceFromAdvert(BroadcastAdvertisement adv) { return new TvDevice(adv.getServiceName(), adv.getServiceAddress(), adv.getServicePort()); } /** * Checks if wifi connectivity is available. * * @return boolean indicating if wifi is available. */ protected boolean isWifiAvailable() { if (wifiManager.isWifiEnabled()) { WifiInfo info = wifiManager.getConnectionInfo(); return (info != null && info.getIpAddress() != 0); } return false; } /** * Retuns wifi network name. * * @return network name. */ protected String getNetworkName() { if (!isWifiAvailable()) { return null; } WifiInfo info = wifiManager.getConnectionInfo(); return (info != null) ? info.getSSID() : null; } /** * Returns the IP address where network broadcasts are sent. * * @return IP address for broadcasts. */ protected Inet4Address getBroadcastAddress() { Inet4Address broadcastAddress; if (!isWifiAvailable()) { return null; } DhcpInfo dhcp = wifiManager.getDhcpInfo(); if (dhcp == null) { return null; } int broadcast = dhcp.ipAddress | ~dhcp.netmask; byte[] broadcastOctets; if (java.nio.ByteOrder.nativeOrder() == java.nio.ByteOrder.BIG_ENDIAN) { broadcastOctets = new byte[] { (byte) ((broadcast >> 24) & 0xff), (byte) ((broadcast >> 16) & 0xff), (byte) ((broadcast >> 8) & 0xff), (byte) (broadcast & 0xff) }; } else { broadcastOctets = new byte[] { (byte) (broadcast & 0xff), (byte) ((broadcast >> 8) & 0xff), (byte) ((broadcast >> 16) & 0xff), (byte) ((broadcast >> 24) & 0xff) }; } try { broadcastAddress = (Inet4Address) InetAddress.getByAddress(broadcastOctets); } catch (IOException e) { broadcastAddress = null; } return broadcastAddress; } /** * Returns Anymote service name. * * @return Anymote service name. */ protected String getServiceName() { return SERVICE_TCP; } }