diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 10 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 12 | ||||
-rw-r--r-- | core/java/android/net/nsd/DnsSdTxtRecord.java | 554 | ||||
-rw-r--r-- | core/java/android/net/nsd/INsdManager.aidl | 29 | ||||
-rw-r--r-- | core/java/android/net/nsd/NetworkServiceInfo.java | 32 | ||||
-rw-r--r-- | core/java/android/net/nsd/NsdManager.java | 394 | ||||
-rw-r--r-- | core/java/com/android/internal/util/Protocol.java | 2 | ||||
-rw-r--r-- | services/java/com/android/server/NsdService.java | 269 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 10 |
10 files changed, 1043 insertions, 270 deletions
@@ -117,6 +117,7 @@ LOCAL_SRC_FILES += \ core/java/android/net/INetworkPolicyListener.aidl \ core/java/android/net/INetworkPolicyManager.aidl \ core/java/android/net/INetworkStatsService.aidl \ + core/java/android/net/nsd/INsdManager.aidl \ core/java/android/nfc/INdefPushCallback.aidl \ core/java/android/nfc/INfcAdapter.aidl \ core/java/android/nfc/INfcAdapterExtras.aidl \ diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index d758eca..5ffceb3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -59,6 +59,8 @@ import android.net.NetworkPolicyManager; import android.net.ThrottleManager; import android.net.IThrottleManager; import android.net.Uri; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; import android.net.wifi.p2p.IWifiP2pManager; @@ -372,6 +374,14 @@ class ContextImpl extends Context { ctx.mMainThread.getHandler()); }}); + registerService(NSD_SERVICE, new ServiceFetcher() { + @Override + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(NSD_SERVICE); + INsdManager service = INsdManager.Stub.asInterface(b); + return new NsdManager(service); + }}); + // Note: this was previously cached in a static variable, but // constructed using mMainThread.getHandler(), so converting // it to be a regular Context-cached service... diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2902504..98ed117 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1769,6 +1769,18 @@ public abstract class Context { public static final String WIFI_P2P_SERVICE = "wifip2p"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.NsdManager} for handling management of network service + * discovery + * + * @hide + * @see #getSystemService + * @see android.net.NsdManager + */ + public static final String NSD_SERVICE = "servicediscovery"; + + + /** * Use with {@link #getSystemService} to retrieve a * {@link android.media.AudioManager} for handling management of volume, * ringer modes and audio routing. diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java index 4e016b9..6d4342c 100644 --- a/core/java/android/net/nsd/DnsSdTxtRecord.java +++ b/core/java/android/net/nsd/DnsSdTxtRecord.java @@ -14,276 +14,292 @@ * See the License for the specific language governing permissions and * limitations under the License. - To do: - - implement remove() - - fix set() to replace existing values + To do: + - implement remove() + - fix set() to replace existing values */ +package android.net.nsd; -package com.apple.dnssd; - - -/** - Object used to construct and parse DNS-SD format TXT records. - For more info see <a href="http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt">DNS-Based Service Discovery</a>, section 6. -*/ - -public class TXTRecord -{ - /* - DNS-SD specifies that a TXT record corresponding to an SRV record consist of - a packed array of bytes, each preceded by a length byte. Each string - is an attribute-value pair. - - The TXTRecord object stores the entire TXT data as a single byte array, traversing it - as need be to implement its various methods. - */ - - static final protected byte kAttrSep = '='; - - protected byte[] fBytes; - - /** Constructs a new, empty TXT record. */ - public TXTRecord() - { fBytes = new byte[0]; } - - /** Constructs a new TXT record from a byte array in the standard format. */ - public TXTRecord( byte[] initBytes) - { fBytes = (byte[]) initBytes.clone(); } - - /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> - @param key - The key name. Must be ASCII, with no '=' characters. - <P> - @param value - Value to be encoded into bytes using the default platform character set. - */ - public void set( String key, String value) - { - byte[] valBytes = (value != null) ? value.getBytes() : null; - this.set( key, valBytes); - } - - /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> - @param key - The key name. Must be ASCII, with no '=' characters. - <P> - @param value - Binary representation of the value. - */ - public void set( String key, byte[] value) - { - byte[] keyBytes; - int valLen = (value != null) ? value.length : 0; - - try { - keyBytes = key.getBytes( "US-ASCII"); - } - catch ( java.io.UnsupportedEncodingException uee) { - throw new IllegalArgumentException(); - } - - for ( int i=0; i < keyBytes.length; i++) - if ( keyBytes[i] == '=') - throw new IllegalArgumentException(); - - if ( keyBytes.length + valLen >= 255) - throw new ArrayIndexOutOfBoundsException(); - - int prevLoc = this.remove( key); - if ( prevLoc == -1) - prevLoc = this.size(); - - this.insert( keyBytes, value, prevLoc); - } - - protected void insert( byte[] keyBytes, byte[] value, int index) - // Insert a key-value pair at index - { - byte[] oldBytes = fBytes; - int valLen = (value != null) ? value.length : 0; - int insertion = 0; - int newLen, avLen; - - // locate the insertion point - for ( int i=0; i < index && insertion < fBytes.length; i++) - insertion += (0xFF & (fBytes[ insertion] + 1)); - - avLen = keyBytes.length + valLen + (value != null ? 1 : 0); - newLen = avLen + oldBytes.length + 1; - - fBytes = new byte[ newLen]; - System.arraycopy( oldBytes, 0, fBytes, 0, insertion); - int secondHalfLen = oldBytes.length - insertion; - System.arraycopy( oldBytes, insertion, fBytes, newLen - secondHalfLen, secondHalfLen); - fBytes[ insertion] = ( byte) avLen; - System.arraycopy( keyBytes, 0, fBytes, insertion + 1, keyBytes.length); - if ( value != null) - { - fBytes[ insertion + 1 + keyBytes.length] = kAttrSep; - System.arraycopy( value, 0, fBytes, insertion + keyBytes.length + 2, valLen); - } - } - - /** Remove a key/value pair from the TXT record. Returns index it was at, or -1 if not found. */ - public int remove( String key) - { - int avStart = 0; - - for ( int i=0; avStart < fBytes.length; i++) - { - int avLen = fBytes[ avStart]; - if ( key.length() <= avLen && - ( key.length() == avLen || fBytes[ avStart + key.length() + 1] == kAttrSep)) - { - String s = new String( fBytes, avStart + 1, key.length()); - if ( 0 == key.compareToIgnoreCase( s)) - { - byte[] oldBytes = fBytes; - fBytes = new byte[ oldBytes.length - avLen - 1]; - System.arraycopy( oldBytes, 0, fBytes, 0, avStart); - System.arraycopy( oldBytes, avStart + avLen + 1, fBytes, avStart, oldBytes.length - avStart - avLen - 1); - return i; - } - } - avStart += (0xFF & (avLen + 1)); - } - return -1; - } - - /** Return the number of keys in the TXT record. */ - public int size() - { - int i, avStart; - - for ( i=0, avStart=0; avStart < fBytes.length; i++) - avStart += (0xFF & (fBytes[ avStart] + 1)); - return i; - } - - /** Return true if key is present in the TXT record, false if not. */ - public boolean contains( String key) - { - String s = null; - - for ( int i=0; null != ( s = this.getKey( i)); i++) - if ( 0 == key.compareToIgnoreCase( s)) - return true; - return false; - } - - /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ - public String getKey( int index) - { - int avStart = 0; - - for ( int i=0; i < index && avStart < fBytes.length; i++) - avStart += fBytes[ avStart] + 1; - - if ( avStart < fBytes.length) - { - int avLen = fBytes[ avStart]; - int aLen = 0; - - for ( aLen=0; aLen < avLen; aLen++) - if ( fBytes[ avStart + aLen + 1] == kAttrSep) - break; - return new String( fBytes, avStart + 1, aLen); - } - return null; - } - - /** - Look up a key in the TXT record by zero-based index and return its value. <P> - Returns null if index exceeds the total number of keys. - Returns null if the key is present with no value. - */ - public byte[] getValue( int index) - { - int avStart = 0; - byte[] value = null; - - for ( int i=0; i < index && avStart < fBytes.length; i++) - avStart += fBytes[ avStart] + 1; - - if ( avStart < fBytes.length) - { - int avLen = fBytes[ avStart]; - int aLen = 0; - - for ( aLen=0; aLen < avLen; aLen++) - { - if ( fBytes[ avStart + aLen + 1] == kAttrSep) - { - value = new byte[ avLen - aLen - 1]; - System.arraycopy( fBytes, avStart + aLen + 2, value, 0, avLen - aLen - 1); - break; - } - } - } - return value; - } - - /** Converts the result of getValue() to a string in the platform default character set. */ - public String getValueAsString( int index) - { - byte[] value = this.getValue( index); - return value != null ? new String( value) : null; - } - - /** Get the value associated with a key. Will be null if the key is not defined. - Array will have length 0 if the key is defined with an = but no value.<P> - - @param forKey - The left-hand side of the key-value pair. - <P> - @return The binary representation of the value. - */ - public byte[] getValue( String forKey) - { - String s = null; - int i; - - for ( i=0; null != ( s = this.getKey( i)); i++) - if ( 0 == forKey.compareToIgnoreCase( s)) - return this.getValue( i); - return null; - } - - /** Converts the result of getValue() to a string in the platform default character set.<P> - - @param forKey - The left-hand side of the key-value pair. - <P> - @return The value represented in the default platform character set. - */ - public String getValueAsString( String forKey) - { - byte[] val = this.getValue( forKey); - return val != null ? new String( val) : null; - } - - /** Return the contents of the TXT record as raw bytes. */ - public byte[] getRawBytes() { return (byte[]) fBytes.clone(); } - - /** Return a string representation of the object. */ - public String toString() - { - String a, result = null; - - for ( int i=0; null != ( a = this.getKey( i)); i++) - { - String av = String.valueOf( i) + "={" + a; - String val = this.getValueAsString( i); - if ( val != null) - av += "=" + val + "}"; - else - av += "}"; - if ( result == null) - result = av; - else - result = result + ", " + av; - } - return result != null ? result : ""; - } +import android.os.Parcelable; +import android.os.Parcel; + +/** + * This class handles TXT record data for DNS based service discovery as specified at + * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 + * + * DNS-SD specifies that a TXT record corresponding to an SRV record consist of + * a packed array of bytes, each preceded by a length byte. Each string + * is an attribute-value pair. + * + * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it + * as need be to implement its various methods. + * + * @hide + */ +public class DnsSdTxtRecord implements Parcelable { + private static final byte mSeperator = '='; + + private byte[] mData; + + /** Constructs a new, empty TXT record. */ + public DnsSdTxtRecord() { + mData = new byte[0]; + } + + /** Constructs a new TXT record from a byte array in the standard format. */ + public DnsSdTxtRecord(byte[] data) { + mData = (byte[]) data.clone(); + } + + /** Copy constructor */ + public DnsSdTxtRecord(DnsSdTxtRecord src) { + if (src != null && src.mData != null) { + mData = (byte[]) src.mData.clone(); + } + } + + /** + * Set a key/value pair. Setting an existing key will replace its value. + * @param key Must be ascii with no '=' + * @param value matching value to key + */ + public void set(String key, String value) { + byte[] keyBytes; + byte[] valBytes; + int valLen; + + if (value != null) { + valBytes = value.getBytes(); + valLen = valBytes.length; + } else { + valBytes = null; + valLen = 0; + } + + try { + keyBytes = key.getBytes("US-ASCII"); + } + catch (java.io.UnsupportedEncodingException e) { + throw new IllegalArgumentException("key should be US-ASCII"); + } + + for (int i = 0; i < keyBytes.length; i++) { + if (keyBytes[i] == '=') { + throw new IllegalArgumentException("= is not a valid character in key"); + } + } + + if (keyBytes.length + valLen >= 255) { + throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); + } + + int currentLoc = remove(key); + if (currentLoc == -1) + currentLoc = keyCount(); + + insert(keyBytes, valBytes, currentLoc); + } + + /** + * Get a value for a key + * + * @param key + * @return The value associated with the key + */ + public String get(String key) { + byte[] val = this.getValue(key); + return val != null ? new String(val) : null; + } + + /** Remove a key/value pair. If found, returns the index or -1 if not found */ + public int remove(String key) { + int avStart = 0; + + for (int i=0; avStart < mData.length; i++) { + int avLen = mData[avStart]; + if (key.length() <= avLen && + (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) { + String s = new String(mData, avStart + 1, key.length()); + if (0 == key.compareToIgnoreCase(s)) { + byte[] oldBytes = mData; + mData = new byte[oldBytes.length - avLen - 1]; + System.arraycopy(oldBytes, 0, mData, 0, avStart); + System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, + oldBytes.length - avStart - avLen - 1); + return i; + } + } + avStart += (0xFF & (avLen + 1)); + } + return -1; + } + + /** Return the count of keys */ + public int keyCount() { + int count = 0, nextKey; + for (nextKey = 0; nextKey < mData.length; count++) { + nextKey += (0xFF & (mData[nextKey] + 1)); + } + return count; + } + + /** Return true if key is present, false if not. */ + public boolean contains(String key) { + String s = null; + for (int i = 0; null != (s = this.getKey(i)); i++) { + if (0 == key.compareToIgnoreCase(s)) return true; + } + return false; + } + + /* Gets the size in bytes */ + public int size() { + return mData.length; + } + + /* Gets the raw data in bytes */ + public byte[] getRawData() { + return mData; + } + + private void insert(byte[] keyBytes, byte[] value, int index) { + byte[] oldBytes = mData; + int valLen = (value != null) ? value.length : 0; + int insertion = 0; + int newLen, avLen; + + for (int i = 0; i < index && insertion < mData.length; i++) { + insertion += (0xFF & (mData[insertion] + 1)); + } + + avLen = keyBytes.length + valLen + (value != null ? 1 : 0); + newLen = avLen + oldBytes.length + 1; + + mData = new byte[newLen]; + System.arraycopy(oldBytes, 0, mData, 0, insertion); + int secondHalfLen = oldBytes.length - insertion; + System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); + mData[insertion] = (byte) avLen; + System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); + if (value != null) { + mData[insertion + 1 + keyBytes.length] = mSeperator; + System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); + } + } + + /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ + private String getKey(int index) { + int avStart = 0; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) break; + } + return new String(mData, avStart + 1, aLen); + } + return null; + } + + /** + * Look up a key in the TXT record by zero-based index and return its value. + * Returns null if index exceeds the total number of keys. + * Returns null if the key is present with no value. + */ + private byte[] getValue(int index) { + int avStart = 0; + byte[] value = null; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) { + value = new byte[avLen - aLen - 1]; + System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); + break; + } + } + } + return value; + } + + private String getValueAsString(int index) { + byte[] value = this.getValue(index); + return value != null ? new String(value) : null; + } + + private byte[] getValue(String forKey) { + String s = null; + int i; + + for (i = 0; null != (s = this.getKey(i)); i++) { + if (0 == forKey.compareToIgnoreCase(s)) { + return this.getValue(i); + } + } + + return null; + } + + /** + * Return a string representation. + * Example : {key1=value1},{key2=value2}.. + * + * For a key say like "key3" with null value + * {key1=value1},{key2=value2}{key3} + */ + public String toString() { + String a, result = null; + + for (int i = 0; null != (a = this.getKey(i)); i++) { + String av = "{" + a; + String val = this.getValueAsString(i); + if (val != null) + av += "=" + val + "}"; + else + av += "}"; + if (result == null) + result = av; + else + result = result + ", " + av; + } + return result != null ? result : ""; + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + /** Implement the Parcelable interface */ + public static final Creator<DnsSdTxtRecord> CREATOR = + new Creator<DnsSdTxtRecord>() { + public DnsSdTxtRecord createFromParcel(Parcel in) { + DnsSdTxtRecord info = new DnsSdTxtRecord(); + in.readByteArray(info.mData); + return info; + } + + public DnsSdTxtRecord[] newArray(int size) { + return new DnsSdTxtRecord[size]; + } + }; } diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl new file mode 100644 index 0000000..077a675 --- /dev/null +++ b/core/java/android/net/nsd/INsdManager.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2012, The Android Open Source Project + * + * 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 android.net.nsd; + +import android.os.Messenger; + +/** + * Interface that NsdService implements + * + * {@hide} + */ +interface INsdManager +{ + Messenger getMessenger(); +} diff --git a/core/java/android/net/nsd/NetworkServiceInfo.java b/core/java/android/net/nsd/NetworkServiceInfo.java new file mode 100644 index 0000000..34d83d1 --- /dev/null +++ b/core/java/android/net/nsd/NetworkServiceInfo.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.net.nsd; + +/** + * Interface for a network service. + * + * {@hide} + */ +public interface NetworkServiceInfo { + + String getServiceName(); + void setServiceName(String s); + + String getServiceType(); + void setServiceType(String s); + +} diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java new file mode 100644 index 0000000..a109a98 --- /dev/null +++ b/core/java/android/net/nsd/NsdManager.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 android.net.nsd; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Messenger; +import android.util.Log; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * The Network Service Discovery Manager class provides the API for service + * discovery. Service discovery enables applications to discover and connect with services + * on a network. Example applications include a game application discovering another instance + * of the game application or a printer application discovering other printers on a network. + * + * <p> The API is asynchronous and responses to requests from an application are on listener + * callbacks provided by the application. The application needs to do an initialization with + * {@link #initialize} before doing any operation. + * + * <p> Android currently supports DNS based service discovery and it is limited to a local + * network with the use of multicast DNS. In future, this class will be + * extended to support other service discovery mechanisms. + * + * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) + * Context.getSystemService(Context.NSD_SERVICE)}. + * @hide + * + */ +public class NsdManager { + private static final String TAG = "NsdManager"; + INsdManager mService; + + private static final int BASE = Protocol.BASE_NSD_MANAGER; + + /** @hide */ + public static final int DISCOVER_SERVICES = BASE + 1; + /** @hide */ + public static final int DISCOVER_SERVICES_STARTED = BASE + 2; + /** @hide */ + public static final int DISCOVER_SERVICES_FAILED = BASE + 3; + /** @hide */ + public static final int SERVICE_FOUND = BASE + 4; + /** @hide */ + public static final int SERVICE_LOST = BASE + 5; + + /** @hide */ + public static final int STOP_DISCOVERY = BASE + 6; + /** @hide */ + public static final int STOP_DISCOVERY_FAILED = BASE + 7; + /** @hide */ + public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8; + + /** @hide */ + public static final int REGISTER_SERVICE = BASE + 9; + /** @hide */ + public static final int REGISTER_SERVICE_FAILED = BASE + 10; + /** @hide */ + public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11; + + /** @hide */ + public static final int UPDATE_SERVICE = BASE + 12; + /** @hide */ + public static final int UPDATE_SERVICE_FAILED = BASE + 13; + /** @hide */ + public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 14; + + /** @hide */ + public static final int RESOLVE_SERVICE = BASE + 15; + /** @hide */ + public static final int RESOLVE_SERVICE_FAILED = BASE + 16; + /** @hide */ + public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17; + + /** + * Create a new Nsd instance. Applications use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}. + * @param service the Binder interface + * @hide - hide this because it takes in a parameter of type INsdManager, which + * is a system private class. + */ + public NsdManager(INsdManager service) { + mService = service; + } + + /** + * Indicates that the operation failed due to an internal error. + */ + public static final int ERROR = 0; + + /** + * Indicates that the operation failed because service discovery is unsupported on the device. + */ + public static final int UNSUPPORTED = 1; + + /** + * Indicates that the operation failed because the framework is busy and + * unable to service the request + */ + public static final int BUSY = 2; + + /** Interface for callback invocation when framework channel is connected or lost */ + public interface ChannelListener { + public void onChannelConnected(Channel c); + /** + * The channel to the framework has been disconnected. + * Application could try re-initializing using {@link #initialize} + */ + public void onChannelDisconnected(); + } + + public interface ActionListener { + + public void onFailure(int errorCode); + + public void onSuccess(); + } + + public interface DnsSdDiscoveryListener { + + public void onFailure(int errorCode); + + public void onStarted(String registrationType); + + public void onServiceFound(DnsSdServiceInfo serviceInfo); + + public void onServiceLost(DnsSdServiceInfo serviceInfo); + + } + + public interface DnsSdRegisterListener { + + public void onFailure(int errorCode); + + public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); + } + + public interface DnsSdUpdateRegistrationListener { + + public void onFailure(int errorCode); + + public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); + } + + public interface DnsSdResolveListener { + + public void onFailure(int errorCode); + + public void onServiceResolved(DnsSdServiceInfo serviceInfo); + } + + /** + * A channel that connects the application to the NetworkService framework. + * Most service operations require a Channel as an argument. An instance of Channel is obtained + * by doing a call on {@link #initialize} + */ + public static class Channel { + Channel(Looper looper, ChannelListener l) { + mAsyncChannel = new AsyncChannel(); + mHandler = new ServiceHandler(looper); + mChannelListener = l; + } + private ChannelListener mChannelListener; + private DnsSdDiscoveryListener mDnsSdDiscoveryListener; + private ActionListener mDnsSdStopDiscoveryListener; + private DnsSdRegisterListener mDnsSdRegisterListener; + private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; + private DnsSdResolveListener mDnsSdResolveListener; + + AsyncChannel mAsyncChannel; + ServiceHandler mHandler; + class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelConnected(Channel.this); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelDisconnected(); + mChannelListener = null; + } + break; + case DISCOVER_SERVICES_STARTED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onStarted((String) message.obj); + } + break; + case DISCOVER_SERVICES_FAILED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onFailure(message.arg1); + } + break; + case SERVICE_FOUND: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceFound( + (DnsSdServiceInfo) message.obj); + } + break; + case SERVICE_LOST: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceLost( + (DnsSdServiceInfo) message.obj); + } + break; + case STOP_DISCOVERY_FAILED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onFailure(message.arg1); + } + break; + case STOP_DISCOVERY_SUCCEEDED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onSuccess(); + } + break; + case REGISTER_SERVICE_FAILED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onFailure(message.arg1); + } + break; + case REGISTER_SERVICE_SUCCEEDED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onServiceRegistered(message.arg1, + (DnsSdServiceInfo) message.obj); + } + break; + case UPDATE_SERVICE_FAILED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onFailure(message.arg1); + } + break; + case UPDATE_SERVICE_SUCCEEDED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onServiceUpdated(message.arg1, + (DnsSdTxtRecord) message.obj); + } + break; + case RESOLVE_SERVICE_FAILED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onFailure(message.arg1); + } + break; + case RESOLVE_SERVICE_SUCCEEDED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onServiceResolved( + (DnsSdServiceInfo) message.obj); + } + break; + default: + Log.d(TAG, "Ignored " + message); + break; + } + } + } + } + + /** + * Registers the application with the service discovery framework. This function + * must be the first to be called before any other operations are performed. No service + * discovery operations must be performed until the ChannelListener callback notifies + * that the channel is connected + * + * @param srcContext is the context of the source + * @param srcLooper is the Looper on which the callbacks are receivied + * @param listener for callback at loss of framework communication. + */ + public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { + Messenger messenger = getMessenger(); + if (messenger == null) throw new RuntimeException("Failed to initialize"); + if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); + + Channel c = new Channel(srcLooper, listener); + c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); + } + + /** + * Set the listener for service discovery. Can be null. + */ + public void setDiscoveryListener(Channel c, DnsSdDiscoveryListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdDiscoveryListener = b; + } + + /** + * Set the listener for stop service discovery. Can be null. + */ + public void setStopDiscoveryListener(Channel c, ActionListener a) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdStopDiscoveryListener = a; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setRegisterListener(Channel c, DnsSdRegisterListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdRegisterListener = b; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setUpdateRegistrationListener(Channel c, DnsSdUpdateRegistrationListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdUpdateListener = b; + } + + /** + * Set the listener for service resolution. Can be null. + */ + public void setResolveListener(Channel c, DnsSdResolveListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdResolveListener = b; + } + + public void registerService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo); + } + + public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); + } + + public void discoverServices(Channel c, String serviceType) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (c.mDnsSdDiscoveryListener == null) throw new + IllegalStateException("Discovery listener needs to be set first"); + DnsSdServiceInfo s = new DnsSdServiceInfo(); + s.setServiceType(serviceType); + c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); + } + + public void stopServiceDiscovery(Channel c) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(STOP_DISCOVERY); + } + + public void resolveService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + if (c.mDnsSdResolveListener == null) throw new + IllegalStateException("Resolve listener needs to be set first"); + c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo); + } + + /** + * Get a reference to NetworkService handler. This is used to establish + * an AsyncChannel communication with the service + * + * @return Messenger pointing to the NetworkService handler + */ + private Messenger getMessenger() { + try { + return mService.getMessenger(); + } catch (RemoteException e) { + return null; + } + } +} diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index d462d7f..7c2b1b5 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -49,7 +49,7 @@ public class Protocol { public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000; - public static final int BASE_DNS_PINGER = 0x00050000; + public static final int BASE_NSD_MANAGER = 0x00060000; //TODO: define all used protocols } diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java new file mode 100644 index 0000000..768be7d --- /dev/null +++ b/services/java/com/android/server/NsdService.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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.android.server; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.nsd.DnsSdServiceInfo; +import android.net.nsd.DnsSdTxtRecord; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Messenger; +import android.os.IBinder; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.List; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.AsyncChannel; +import com.android.server.am.BatteryStatsService; +import com.android.server.NativeDaemonConnector.Command; +import com.android.internal.R; + +/** + * Network Service Discovery Service handles remote service discovery operation requests by + * implementing the INsdManager interface. + * + * @hide + */ +public class NsdService extends INsdManager.Stub { + private static final String TAG = "NsdService"; + private static final String MDNS_TAG = "mDnsConnector"; + + private static final boolean DBG = true; + + private Context mContext; + + /** + * Clients receiving asynchronous messages + */ + private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>(); + + private AsyncChannel mReplyChannel = new AsyncChannel(); + + /** + * Handles client(app) connections + */ + private class AsyncServiceHandler extends Handler { + + AsyncServiceHandler(android.os.Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + AsyncChannel c = (AsyncChannel) msg.obj; + if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); + c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); + mClients.add(c); + } else { + Slog.e(TAG, "Client connection failure, error=" + msg.arg1); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.e(TAG, "Send failed, client connection lost"); + } else { + if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + } + mClients.remove((AsyncChannel) msg.obj); + break; + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: + AsyncChannel ac = new AsyncChannel(); + ac.connect(mContext, this, msg.replyTo); + break; + case NsdManager.DISCOVER_SERVICES: + if (DBG) Slog.d(TAG, "Discover services"); + DnsSdServiceInfo s = (DnsSdServiceInfo) msg.obj; + discoverServices(1, s.getServiceType()); + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED); + break; + case NsdManager.STOP_DISCOVERY: + if (DBG) Slog.d(TAG, "Stop service discovery"); + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED); + break; + case NsdManager.REGISTER_SERVICE: + if (DBG) Slog.d(TAG, "Register service"); + mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED); + break; + case NsdManager.UPDATE_SERVICE: + if (DBG) Slog.d(TAG, "Update service"); + mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED); + break; + default: + Slog.d(TAG, "NsdServicehandler.handleMessage ignoring msg=" + msg); + break; + } + } + } + private AsyncServiceHandler mAsyncServiceHandler; + + private NativeDaemonConnector mNativeConnector; + private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1); + + private NsdService(Context context) { + mContext = context; + + HandlerThread nsdThread = new HandlerThread("NsdService"); + nsdThread.start(); + mAsyncServiceHandler = new AsyncServiceHandler(nsdThread.getLooper()); + + /* + mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10, + MDNS_TAG, 25); + Thread th = new Thread(mNativeConnector, MDNS_TAG); + th.start(); + */ + } + + public static NsdService create(Context context) throws InterruptedException { + NsdService service = new NsdService(context); + /* service.mNativeDaemonConnected.await(); */ + return service; + } + + public Messenger getMessenger() { + return new Messenger(mAsyncServiceHandler); + } + + /* These should be in sync with system/netd/mDnsResponseCode.h */ + class NativeResponseCode { + public static final int SERVICE_FOUND = 101; + public static final int SERVICE_LOST = 102; + public static final int SERVICE_DISCOVERY_FAILED = 103; + + public static final int SERVICE_REGISTERED = 104; + public static final int SERVICE_REGISTRATION_FAILED = 105; + + public static final int SERVICE_UPDATED = 106; + public static final int SERVICE_UPDATE_FAILED = 107; + + public static final int SERVICE_RESOLVED = 108; + public static final int SERVICE_RESOLUTION_FAILED = 109; + } + + + class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks { + public void onDaemonConnected() { + mNativeDaemonConnected.countDown(); + } + + public boolean onEvent(int code, String raw, String[] cooked) { + switch (code) { + case NativeResponseCode.SERVICE_FOUND: + /* NNN uniqueId serviceName regType */ + break; + case NativeResponseCode.SERVICE_LOST: + /* NNN uniqueId serviceName regType */ + break; + case NativeResponseCode.SERVICE_DISCOVERY_FAILED: + /* NNN uniqueId errorCode */ + break; + case NativeResponseCode.SERVICE_REGISTERED: + /* NNN regId serviceName regType */ + break; + case NativeResponseCode.SERVICE_REGISTRATION_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_UPDATED: + /* NNN regId */ + break; + case NativeResponseCode.SERVICE_UPDATE_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_RESOLVED: + /* NNN resolveId fullName hostName port txtlen txtdata */ + break; + case NativeResponseCode.SERVICE_RESOLUTION_FAILED: + /* NNN resovleId errorCode */ + break; + default: + break; + } + return false; + } + } + + private void registerService(int regId, DnsSdServiceInfo service) { + try { + //Add txtlen and txtdata + mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(), + service.getServiceType(), service.getPort()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to execute registerService"); + } + } + + private void updateService(int regId, DnsSdTxtRecord t) { + try { + if (t == null) return; + mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to updateServices"); + } + } + + private void discoverServices(int discoveryId, String serviceType) { + try { + mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to discoverServices"); + } + } + + private void stopServiceDiscovery(int discoveryId) { + try { + mNativeConnector.execute("mdnssd", "stopdiscover", discoveryId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to stopServiceDiscovery"); + } + } + + private void resolveService(DnsSdServiceInfo service) { + try { + mNativeConnector.execute("mdnssd", "resolve", service.getServiceName(), + service.getServiceType()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to resolveService"); + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("Internal state:"); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 423dad6..e091edf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -120,6 +120,7 @@ class ServerThread extends Thread { ConnectivityService connectivity = null; WifiP2pService wifiP2p = null; WifiService wifi = null; + NsdService serviceDiscovery= null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; @@ -394,6 +395,15 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Network Service Discovery Service"); + serviceDiscovery = NsdService.create(context); + ServiceManager.addService( + Context.NSD_SERVICE, serviceDiscovery); + } catch (Throwable e) { + reportWtf("starting Service Discovery Service", e); + } + + try { Slog.i(TAG, "Throttle Service"); throttle = new ThrottleService(context); ServiceManager.addService( |