Android Open Source - ubihelper Peer Manager






From Project

Back to project page ubihelper.

License

The source code is released under:

GNU General Public License

If you think the Android project ubihelper listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Copyright (c) 2012 The University of Nottingham
 * /* ww w  .ja  v a  2s  .  co  m*/
 * This file is part of ubihelper
 *
 *  ubihelper is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  ubihelper is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with ubihelper. If not, see <http://www.gnu.org/licenses/>.
 *  
 *  @author Chris Greenhalgh (cmg@cs.nott.ac.uk), The University of Nottingham
 */
package uk.ac.horizon.ubihelper.service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.http.impl.cookie.BrowserCompatSpecFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import uk.ac.horizon.ubihelper.R;
import uk.ac.horizon.ubihelper.R.drawable;
import uk.ac.horizon.ubihelper.channel.NamedChannel;
import uk.ac.horizon.ubihelper.dns.DnsClient;
import uk.ac.horizon.ubihelper.dns.DnsProtocol;
import uk.ac.horizon.ubihelper.dns.DnsUtils;
import uk.ac.horizon.ubihelper.dns.DnsProtocol.RR;
import uk.ac.horizon.ubihelper.net.Message;
import uk.ac.horizon.ubihelper.net.OnPeerConnectionListener;
import uk.ac.horizon.ubihelper.net.PeerConnection;
import uk.ac.horizon.ubihelper.net.PeerConnectionScheduler;
import uk.ac.horizon.ubihelper.protocol.ClientInfo;
import uk.ac.horizon.ubihelper.protocol.ClientState;
import uk.ac.horizon.ubihelper.protocol.MessageUtils;
import uk.ac.horizon.ubihelper.protocol.PeerInfo;
import uk.ac.horizon.ubihelper.protocol.ProtocolManager;
import uk.ac.horizon.ubihelper.protocol.ProtocolManager.ClientConnectionListener;
import uk.ac.horizon.ubihelper.service.PeerManager.SearchInfo;
import uk.ac.horizon.ubihelper.ui.PeerRequestInfoActivity;
import uk.ac.horizon.ubihelper.ui.PeerRequestActivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.telephony.TelephonyManager;
import android.util.Base64;
import android.util.Log;

/** Manages all interactions with Peers
 * 
 * @author cmg
 *
 */
public class PeerManager {
  public static final String TAG = "ubihelper-peermgr";
  private Service service;
  private boolean closed = false;
  /** for initial discovery search */
  private DnsClient searchClient = null;
  private WifiManager wifi = null;
  private DnsClient.OnChange onSearchChangeListener = new OnSearchChangeListener();
  private boolean searchStarted = false;
  private ServerSocketChannel serverSocketChannel;
  private int serverPort;
  private PeerConnectionScheduler selector;
  private MessageDigest messageDigest;
  private MyProtocolManager protocol;
  private SQLiteDatabase database;
  /** partial, id -> PeerInfo */
  private HashMap<String,PeerInfo> peerInfoCache = new HashMap<String,PeerInfo>();
  private java.util.Timer remoteChannelTimer;
  
  public static class SearchInfo {
    public String name;
    public InetAddress src;
    public String toString() {
      return name;
    }
  }
  
  public static final String ACTION_SEARCH_STARTED = "uk.ac.horizon.ubihelper.action.SEARCH_STARTED";
  public static final String ACTION_SEARCH_STOPPED = "uk.ac.horizon.ubihelper.action.SEARCH_STOPPED";
  /** Broadcast, has extra NAME & SOURCEIP */
  public static final String ACTION_PEER_DISCOVERED = "uk.ac.horizon.ubihelper.action.PEER_DISCOVERED";
  /** Broadcast, has extra NAME, SOURCEIP, PEER_STATE & (optionally) PORT */
  public static final String ACTION_PEER_REQUEST_STATE_CHANGED = "uk.ac.horizon.ubihelper.action.PEER_REQUEST_STATE_CHANGED";
  public static final String EXTRA_NAME = "uk.ac.horizon.ubihelper.extra.NAME";
  public static final String EXTRA_SOURCEIP = "uk.ac.horizon.ubihelper.extra.SOURCEIP";
  public static final String EXTRA_PORT = "uk.ac.horizon.ubihelper.extra.PORT";
  public static final String EXTRA_PEER_STATE = "uk.ac.horizon.ubihelper.extra.PEER_STATE";
  public static final String EXTRA_DETAIL = "uk.ac.horizon.ubihelper.extra.DETAIL";
  public static final String EXTRA_ID = "uk.ac.horizon.ubihelper.extra.ID";
  public static final String ACTION_PEER_REQUESTS_CHANGED = "uk.ac.horizon.ubihelper.action.PEER_REQUESTS_CHANGED";

  public static final String ACTION_PEERS_CHANGED = "uk.ac.horizon.ubihelper.action.PEERS_CHANGED";
  public static final String ACTION_PEER_STATE_CHANGED = "uk.ac.horizon.ubihelper.action.PEER_STATE_CHANGED";

  private static final long MAX_QUERY_AGE = 15000;
  
  /** info keys */
  private static final String KEY_IMEI = "imei";
  private static final String KEY_WIFIMAC = "wifimac";
  private static final String KEY_BTMAC = "btmac";
  public static final String KEY_NAME = "name";
  public static final String KEY_PERIOD = "period";
  public static final String KEY_TIMEOUT = "timeout";
  
  public PeerManager(Service service) {    
    this.service = service;
    // Note: meant to open database on another thread?!
    database = new PeersOpenHelper(service).getWritableDatabase();

    protocol = new MyProtocolManager();
    peerConnectionListener = new OnPeerConnectionListener(protocol);
    remoteChannelTimer = new Timer();
    
    wifi = (WifiManager)service.getSystemService(Service.WIFI_SERVICE);
    
    try {
      messageDigest = MessageDigest.getInstance("MD5");
    }
    catch (Exception e) {
      Log.e(TAG,"Could not get MessageDigest: "+e);
    }
    try {
      serverSocketChannel = ServerSocketChannel.open();
      ServerSocket ss = serverSocketChannel.socket();
      ss.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"),0));
      serverPort = ss.getLocalPort();
      serverSocketChannel.configureBlocking(false);
    } catch (IOException e) {
      Log.w(TAG,"Error opening ServerSocketChannel: "+e.getMessage());
    }
    try {
      selector = new PeerConnectionScheduler(serverSocketChannel);
      selector.setListener(selectorListener);
      selector.start();
    } catch (IOException e) {
      Log.w(TAG,"Error starting Selector: "+e.getMessage());
    }
  }
  public synchronized int getServerPort() {
    return serverPort;
  }
  public synchronized void close() {
    closed = true;
    closeInternal();
  }
  public SQLiteDatabase getDatabase() {
    return database;
  }
  private synchronized void closeInternal() {    
    closeSearchInternal();
    if (serverSocketChannel!=null) {
      try {
        serverSocketChannel.close();
      } catch (IOException e) {
      }
      serverSocketChannel = null;
    }
    if (selector!=null) {
      selector.close();
      selector = null;
    }
    if (database!=null) {
      database.close();
      database = null;
    }
    if (remoteChannelTimer!=null) {
      remoteChannelTimer.cancel();
      remoteChannelTimer = null;
    }
  }
  private synchronized void closeSearchInternal() {    
    if (searchClient!=null) {
       searchClient.close();
       searchClient = null;
    }
    if (searchStarted) {
      Intent i = new Intent(ACTION_SEARCH_STOPPED);
      service.sendBroadcast(i);
      searchStarted = false;
    }
  }
  public synchronized boolean isSearchActive() {
    return (searchClient!=null && !searchClient.getDone());
  }
  public synchronized LinkedList<SearchInfo> getSearchAnswers() {
    LinkedList<SearchInfo> sis = new LinkedList<SearchInfo> ();
    if (searchClient==null)
      return sis;
    LinkedList<DnsProtocol.RR> as = searchClient.getAnswers();
    for (DnsProtocol.RR a : as) {
      SearchInfo si = getSearchInfo(a);
      if (si!=null)
        sis.add(si);
    }
    return sis;
  }
  /** return OK if started; false if couldn't, e.g. no wifi active */
  public synchronized boolean startSearch() {
    closeSearchInternal();
    DnsProtocol.Query query = new DnsProtocol.Query();
    query.name = DnsUtils.getServiceDiscoveryName();
    query.rclass = DnsProtocol.CLASS_IN;
    query.type = DnsProtocol.TYPE_PTR;
    searchClient = new DnsClient(query, true);
    searchClient.setOnChange(onSearchChangeListener);
    NetworkInterface ni = getNetworkInterface();
    if (ni==null) {
      Log.w(TAG,"Could not start Dns search - no NetworkInterface");
      return false;
    }
    searchClient.setNetworkInterface(ni);
    searchClient.start();
    searchStarted = true;
    Intent i = new Intent(ACTION_SEARCH_STARTED);
    service.sendBroadcast(i);
    return true;
  }
  private NetworkInterface getNetworkInterface() {
    if (!wifi.isWifiEnabled()) {
      Log.d(TAG,"wifi not enabled");
      return null;
    }
    switch (wifi.getWifiState()) {
    case WifiManager.WIFI_STATE_ENABLED:
      // OK
      break;
    case WifiManager.WIFI_STATE_ENABLING:
      Log.d(TAG, "Wifi enabling");
      return null;
    case WifiManager.WIFI_STATE_DISABLING:
    case WifiManager.WIFI_STATE_DISABLED:
      Log.d(TAG, "wifi disabled/disabling");
      return null;
    default:
      Log.d(TAG, "Wifi state unknown");
      return null;
    }
    // has address?
    WifiInfo info = wifi.getConnectionInfo();
    int ip = info.getIpAddress();
    if (ip==0) {
      Log.d(TAG,"waiting for an IP address");
      return null;
    }
    
    NetworkInterface ni = DnsUtils.getNetworkInterface(ip);
    return ni;
  }
  private class OnSearchChangeListener implements DnsClient.OnChange {
    public void onAnswer(final RR rr) {
      SearchInfo si = getSearchInfo(rr);
      if (si!=null) {
        Intent i = new Intent(ACTION_PEER_DISCOVERED);
        i.putExtra(EXTRA_NAME, si.name);
        i.putExtra(EXTRA_SOURCEIP, si.src.getHostAddress());
        service.sendBroadcast(i);
      }      
    }
    public void onComplete(String error) {
      closeSearchInternal();
    }    
  }
  private static SearchInfo getSearchInfo(DnsProtocol.RR rr) {
    if (rr.type==DnsProtocol.TYPE_PTR) {
      try {
        SearchInfo pi = new SearchInfo();
        String ns[] = DnsProtocol.ptrFromData(rr.rdata);
        if (ns!=null && ns.length>0)
          pi.name = ns[0];
        pi.src = rr.src;
        return pi;
      } catch (IOException e) {
        Log.w(TAG,"Error decoding PTR record: "+e.getMessage());
      }
    }
    return null;
  }
  /** current queries - by name */
  private HashMap<String,ArrayList<DnsClient>> dnsClients = new HashMap<String,ArrayList<DnsClient>>();
  private DnsClient getDnsClient(String name, int type, InetAddress dest) {
    ArrayList<DnsClient> dcs = dnsClients.get(name);
    for (int i=0; dcs!=null && i<dcs.size(); i++) {
      DnsClient dc  = dcs.get(i);
      if (dc.getAge() > MAX_QUERY_AGE) {
        dcs.remove(i);
        i--;
        continue;
      }
      if (type==dc.getQuery().type && (dest==null || dest.equals(dc.getDestination())))
        return dc;
    }
    return null;
  }
  /** peer state */
  public static enum PeerRequestState {
    STATE_SEARCHED_ADD,
    STATE_SRV_DISCOVERY,
    STATE_SRV_DISCOVERY_FAILED,
    STATE_SRV_FOUND,
    STATE_CONNECTING,
    STATE_CONNECTED,
    STATE_CONNECTING_FAILED,
    STATE_NEGOTIATE_PROTOCOL,
    STATE_PEER_REQ,
    STATE_PEER_DONE,
    STATE_PEERED, 
    STATE_PEERED_UNTRUSTED
  }
  /** peer info */
  public static class PeerRequestInfo implements Cloneable {
    public int _id = -1;
    public PeerRequestState state;
    // Search
    public String instanceName;
    public InetAddress src;
    // SRV
    public int port = 0;
    // connect
    PeerConnection pc;
    // peer negotiation
    String pin;
    String pinnonce;
    String pindigest;
    String id;
    String secret1, secret2;
    // done
    public JSONObject peerInfo;
    
    // debug
    public String detail;
    public boolean manual;
    
    PeerRequestInfo(String instanceName, InetAddress src) {
      this.instanceName = instanceName;
      this.src = src;
      state = PeerRequestState.STATE_SEARCHED_ADD;
    }    
    PeerRequestInfo(PeerRequestInfo pi) {
      id = pi.id;
      state = pi.state;
      instanceName = pi.instanceName;
      src = pi.src;
      port = pi.port;
      detail = pi.detail;
    }
    public PeerRequestInfo(ClientInfo ci) {
      id = ci.id;
      instanceName = ci.name;
      pc = ci.pc;
      port = ci.port;
    }
  }
  private void broadcastPeerState(PeerRequestInfo pi) {
    Intent i = new Intent(ACTION_PEER_REQUEST_STATE_CHANGED);
    i.putExtra(EXTRA_PEER_STATE, pi.state.ordinal());
    i.putExtra(EXTRA_NAME, pi.instanceName);
    if (pi.detail!=null)
      i.putExtra(EXTRA_DETAIL, pi.detail);
    i.putExtra(EXTRA_SOURCEIP, pi.src.getHostAddress());
    if (pi.port!=0)
      i.putExtra(EXTRA_PORT, pi.port);
    // for pass over to PeerInfo
    if (pi.id!=null)
      i.putExtra(EXTRA_ID, pi.id);
    //Log.d(TAG,"sendBroadcast (peer state)...");
    service.sendBroadcast(i);
    //Log.d(TAG,"sendBroadcast done");
  }
  private ArrayList<PeerRequestInfo> peerRequests = new ArrayList<PeerRequestInfo>();

  /** public API - list peers */
  public synchronized List<PeerRequestInfo> getPeerRequests() {
    ArrayList<PeerRequestInfo> rpeers = new ArrayList<PeerRequestInfo>();
    for (PeerRequestInfo pi : peerRequests) {
      rpeers.add(new PeerRequestInfo(pi));
    }
    return rpeers;
  }
  /** public API - get info on peer */
  public synchronized PeerRequestInfo getPeer(Intent i) {
    String sourceip = i.getExtras().getString(EXTRA_SOURCEIP);
    String name = i.getExtras().getString(EXTRA_NAME);
    for (PeerRequestInfo pi : peerRequests) {
      if (pi.instanceName.equals(name) && pi.src.getHostAddress().equals(sourceip))
        return pi;
    }
    return null;
  }
  /** public API - matches */
  public static synchronized boolean matches(PeerRequestInfo pi, Intent i) {
    String sourceip = i.getExtras().getString(EXTRA_SOURCEIP);
    String name = i.getExtras().getString(EXTRA_NAME);
    if (pi.instanceName.equals(name) && pi.src.getHostAddress().equals(sourceip))
      return true;
    return false;
  }
  /** public API - matches */
  public static synchronized boolean matches(PeerRequestInfo pi, SearchInfo si) {
    if (pi.instanceName.equals(si.name) && pi.src.equals(si.src))
      return true;
    return false;
  }
  /** public API - start adding a discovered peer */
  public synchronized void addPeer(SearchInfo peerInfo) {
    // already in progress?
    for (PeerRequestInfo pi : peerRequests) {
      if (matches(pi, peerInfo)) {
        // kick?
        return;
      }
    }
    PeerRequestInfo pi = new PeerRequestInfo(peerInfo.name, peerInfo.src);
    peerRequests.add(pi);
    Intent i = new Intent(ACTION_PEER_REQUESTS_CHANGED);
    service.sendBroadcast(i);
    updatePeer(pi);
  }
  private synchronized void updatePeer(PeerRequestInfo pi) {
    switch(pi.state) {
    case STATE_SEARCHED_ADD:
      startSrvDiscovery(pi);
      break;
    case STATE_SRV_DISCOVERY_FAILED:
    case STATE_CONNECTING_FAILED:
      broadcastPeerState(pi);
      removePeer(pi);
      break;
    case STATE_SRV_FOUND:
      connectPeer(pi);
      break;
    case STATE_CONNECTED:
      negotiateProtocol(pi);
      break;
    }
  }
  private OnPeerConnectionListener peerConnectionListener;
  private class OnPeerConnectionListener extends ClientConnectionListener {
    public OnPeerConnectionListener(ProtocolManager pm) {
      super(pm);
    }

    public void onRecvMessage(PeerConnection pc) {
      if (pc.attachment() instanceof PeerRequestInfo) {
        PeerRequestInfo pi = (PeerRequestInfo)pc.attachment();
        Log.d(TAG,"onMessage PeerInfo "+pi);
        checkMessages(pi);
      }
      else super.onRecvMessage(pc);
    }
    
    public void onFail(PeerConnection pc, boolean sendFailed,
        boolean recvFailed, boolean connectFailed) {
      if (pc.attachment() instanceof PeerRequestInfo) {
        PeerRequestInfo pi = (PeerRequestInfo)pc.attachment();
        Log.d(TAG, "onFail PeerInfo "+pi);
        pi.state = PeerRequestState.STATE_CONNECTING_FAILED;
        pi.detail = null;
        updatePeer(pi);
      }
      else super.onFail(pc, sendFailed, recvFailed, connectFailed);
    }

    public void onConnected(PeerConnection pc) {
      if (pc.attachment() instanceof PeerRequestInfo) {
        PeerRequestInfo pi = (PeerRequestInfo)pc.attachment();
        if (pi.state==PeerRequestState.STATE_CONNECTING) {
          Log.d(TAG, "onConnected -> connected PeerInfo "+pi);
          pi.state = PeerRequestState.STATE_CONNECTED;
          pi.detail = null;
          broadcastPeerState(pi);
          updatePeer(pi);
        }
        else
          Log.d(TAG,"onConnected "+pi.state+" PeerInfo "+pi);
      }
      else super.onConnected(pc);
    }
  };
  
  private synchronized void connectPeer(PeerRequestInfo pi) {
    if (pi.src==null) {
      pi.state = PeerRequestState.STATE_CONNECTING_FAILED;
      pi.detail = "IP unknown";
      updatePeer(pi);
      return;
    }
    pi.state = PeerRequestState.STATE_CONNECTING;
    pi.detail = "connectPeer()";
    try {
      Log.d(TAG,"Connect to "+pi.src.getHostAddress()+":"+pi.port);
      pi.pc = selector.connect(new InetSocketAddress(pi.src, pi.port), peerConnectionListener, pi);
      //Log.d(TAG,"Connect done="+done);
      //pi.detail = "4 (done="+done+")";
      boolean done = pi.pc.isConnected();
      if (done) {
        //Toast.makeText(service, "Connected!", Toast.LENGTH_SHORT).show();
        pi.state = PeerRequestState.STATE_CONNECTED;
        pi.detail = "Connected immediately";
      }
      else {
        //Toast.makeText(service, "waiting...", Toast.LENGTH_SHORT).show();
        pi.detail = "Waiting for connect";
        //Log.d(TAG,"Register (connect)...");
        //Log.d(TAG,"registered");
      }
      //Log.d(TAG,"Broadcast...");
      broadcastPeerState(pi);
      //Log.d(TAG,"Broadcast done");
      if (done) {
        updatePeer(pi);
      }
      return;
    } catch (Exception e) {
      // Not just IOExceptions?!
      Log.w(TAG,"Problem connecting to peer "+pi.src.getHostAddress()+":"+pi.port+": "+e.getMessage());
      //Toast.makeText(service, "Error connecting: "+e, Toast.LENGTH_LONG).show();
      pi.state = PeerRequestState.STATE_CONNECTING_FAILED;
      pi.detail = e.getMessage();
      updatePeer(pi);
    }    
  }
  
  private class MyProtocolManager extends ProtocolManager {

    @Override
    public void removeClient(ClientInfo ci) {
      hideClientNotification(ci);
      super.removeClient(ci);
      clients.remove(ci);
    }
    @Override
    protected byte [] base64Decode(String str) {
      return Base64.decode(str, Base64.DEFAULT);
    }
    @Override
    protected String base64Encode(byte [] bs) {
      return Base64.encodeToString(bs, Base64.DEFAULT);
    }
    /** prompt for PIN, e.g. from user. return false (default) if cannot prompt. */
    @Override
    protected boolean clientPromptForPin(ClientInfo ci) {
      // Notification?
      // create taskbar notification
      int icon = R.drawable.peer_request_notification_icon;
      CharSequence tickerText = "Peer request from "+ci.name;
      long when = System.currentTimeMillis();

      Notification notification = new Notification(icon, tickerText, when);
      
      Context context = service;
      CharSequence contentTitle = "Peer request from "+ci.name;
      CharSequence contentText = "Accept or reject peer request from "+ci.name;
      Intent notificationIntent = new Intent(service,  PeerRequestActivity.class);
      notificationIntent.putExtra(EXTRA_ID, ci.id);
      notificationIntent.putExtra(EXTRA_NAME, ci.name);
      PendingIntent contentIntent = PendingIntent.getActivity(service, 0, notificationIntent, 0);

      notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
      peerRequestNotificationId++;
      ci.notificationId = peerRequestNotificationId;
      NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(Service.NOTIFICATION_SERVICE);
      //service.(ci.notificationId, notification);  
      mNotificationManager.notify(ci.notificationId, notification);
      
      return true;
    }
    /** called when successfully peered */
    @Override
    protected boolean clientHandlePeered(ClientInfo ci, PeerInfo pi) {
      clients.remove(ci);
      addPeer(pi);
//      peerCache.put(pi.id, pi);
//      pi.pc.attach(pi);
//
//      Intent i = new Intent(ACTION_PEER_REQUESTS_CHANGED);
//      service.sendBroadcast(i);

      // don't handle more messages as a client
      return false;
    }
    @Override
    protected void peerHandleFailed(PeerInfo pi) {
      // TODO Auto-generated method stub
      Log.d(TAG, "Peer failed: "+pi.id);
      // TODO fast response?
    }

    @Override
    protected JSONObject getInfo() {
      return PeerManager.this.getInfo();
    }
    @Override
    protected String getName() {
      return service.getDeviceName();
    }
    @Override
    protected int getPort() {
      return serverPort;
    }
    @Override
    protected String getId() {
      return getDeviceId();
    }

  }
  protected synchronized void failPeer(PeerRequestInfo pi, String detail) {
    pi.state = PeerRequestState.STATE_CONNECTING_FAILED;
    pi.detail = detail;
    broadcastPeerState(pi);
    removePeer(pi);
  }
  private synchronized void addPeer(PeerInfo pi) {
    // in database?
    if (pi.id==null) {
      Log.e(TAG,"addPeer with null id");
      return;
    }
    database.beginTransaction();
    try {
      PeerInfo pi2 = PeersOpenHelper.getPeerInfo(database, pi.id);
      if (pi2!=null) {
        // update!
        pi._id = pi2._id;
        pi.enabled = pi2.enabled;
        Log.d(TAG,"Updating peer "+pi.id+" on addPeer to "+pi);
        PeersOpenHelper.updatePeerInfo(database, pi);
      }
      else {
        // add
        PeersOpenHelper.addPeerInfo(database, pi);
        Log.d(TAG,"Added Peer "+pi.id+" as "+pi._id);
      }
      database.setTransactionSuccessful();
      // add to peers
      peerInfoCache.put(pi.id, pi);
      // broadcast etc.!
      Intent i = new Intent(ACTION_PEERS_CHANGED);
      service.sendBroadcast(i);
    }
    finally {
      database.endTransaction();
    }
  }
  protected synchronized void checkMessages(PeerRequestInfo pi) {
    Message m = null;
    while ((m=pi.pc.getMessage())!=null) {
      switch (pi.state){
      case STATE_NEGOTIATE_PROTOCOL: {
        boolean ok = MessageUtils.checkNegotiateProtocolResponse(m);
        if (!ok) {
          failPeer(pi, "Incompatible protocol: "+m.body);
          return;
        }
        pi.state = PeerRequestState.STATE_PEER_REQ;
        pi.detail = null;
        broadcastPeerState(pi);
        sendPeerRequest(pi);
        break;
      }
      case STATE_PEER_REQ: {
        // should get pin back (but in future could get something else, e.g.already known)
        boolean ok = handlePeerReqResponse(pi, m);
        if (!ok)
          return;
        break;
      }
      case STATE_PEER_DONE: {
        boolean ok = handlePeerDoneResponse(pi, m);
        if (!ok)
          return;
        break;
      }
      default:
        Log.w(TAG,"Don't know what to do with message in state "+pi.state);
        return;
      }
    }    
    
  }
  private synchronized void startSrvDiscovery(final PeerRequestInfo pi) {
    // 1: do SRV discovery to get IP/Port
    String name = DnsUtils.getServiceDiscoveryName();
    DnsClient dc = getDnsClient(name, DnsProtocol.TYPE_SRV, pi.src);
    if (dc==null) {
      // start again
      DnsProtocol.Query query = new DnsProtocol.Query();
      query.name = DnsUtils.getServiceDiscoveryName();
      query.type = DnsProtocol.TYPE_SRV;
      query.rclass = DnsProtocol.CLASS_IN;
      final DnsClient fdc = new DnsClient(query, false);
      NetworkInterface ni = getNetworkInterface();
      if (ni!=null)
        fdc.setNetworkInterface(ni);
      fdc.setDestination(pi.src);
      fdc.setOnChange(new DnsClient.OnChange() {
        public void onAnswer(RR rr) {
        }
        public void onComplete(String error) {
          service.postTask(new Runnable() {
            public void run() {
              srvDiscoveryComplete(fdc, pi.src);              
            }            
          });
        }
      });
      ArrayList<DnsClient> dcs = dnsClients.get(name);
      if (dcs==null) {
        dcs = new ArrayList<DnsClient>();
        dnsClients.put(name, dcs);
      }
      dcs.add(fdc);
      pi.state = PeerRequestState.STATE_SRV_DISCOVERY;
      broadcastPeerState(pi);
      fdc.start();
      return;
    }
    else {
      pi.state = PeerRequestState.STATE_SRV_DISCOVERY;
      // handle result? (not too old, but may have finished, or not)
      boolean done = dc.getDone();
      if (done) {
        srvDiscoveryComplete(dc, pi.src);
      }
      else
        broadcastPeerState(pi);
      // otherwise will be done...
      return;
    }
  }
    // 2: initiate connection
    // 3: generate and send peer request
    // 4: wait for and handle response     
  private synchronized void srvDiscoveryComplete(DnsClient dc, InetAddress src) {
    // copy peers in case of delete
    ArrayList<PeerRequestInfo> peers2 = new ArrayList<PeerRequestInfo>();
    peers2.addAll(peerRequests);
    for (int i=0; i<peers2.size(); i++) {
      PeerRequestInfo pi = peers2.get(i);
      if (pi.state==PeerRequestState.STATE_SRV_DISCOVERY && pi.src.equals(src)) {
        LinkedList<DnsProtocol.RR> as = dc.getAnswers();
        if (as.size()==0) {
          pi.state = PeerRequestState.STATE_SRV_DISCOVERY_FAILED;
          updatePeer(pi);
        } else {
          try {
            DnsProtocol.SrvData srv = DnsProtocol.srvFromData(as.get(0).rdata);
            pi.state = PeerRequestState.STATE_SRV_FOUND;
            pi.port = srv.port;
            if (!srv.target.equals(src.getHostAddress())) {
              Log.w(TAG,"SRV returned different IP: "+srv.target+" vs "+src.getHostAddress());
            }
            updatePeer(pi);
          } catch (IOException e) {
            Log.w(TAG,"Error parsing SRV data: "+e.getMessage());
            pi.state = PeerRequestState.STATE_SRV_DISCOVERY_FAILED;          
            updatePeer(pi);
          }          
        }
      }
    }
  }
  private synchronized void removePeer(PeerRequestInfo pi) {
    if (peerRequests.remove(pi))
    {
      Intent i = new Intent(ACTION_PEER_REQUESTS_CHANGED);
      service.sendBroadcast(i);
    }
    if (pi.pc!=null) {
      pi.pc.close();
      pi.pc = null;
    }
  }
  
  private synchronized void negotiateProtocol(PeerRequestInfo pi) {
    // Start negotiation on PeerConnection...
    // protocol hello
    String protocol = Message.getHelloBody();
    Message m = new Message(Message.Type.HELLO, null, null, protocol);
    pi.pc.sendMessage(m);
    pi.state = PeerRequestState.STATE_NEGOTIATE_PROTOCOL;
    broadcastPeerState(pi);
    return;
  }    
  private synchronized void sendPeerRequest(PeerRequestInfo pi) {
    
    // create peer request
    JSONObject msg = new JSONObject();
    try {
      msg.put(MessageUtils.KEY_TYPE, MessageUtils.MSG_INIT_PEER_REQ);
      // pass key
      byte pbuf[] = new byte[4];
      protocol.getRandom(pbuf);
      StringBuilder pb = new StringBuilder();
      for (int i=0; i<pbuf.length; i++) 
        pb.append((char)('0'+((pbuf[i]&0xff) % 10)));
      pi.pin = pb.toString();
      // pin nonce and digest
      byte nbuf[] = new byte[8];
      protocol.getRandom(nbuf);
      pi.pinnonce = Base64.encodeToString(nbuf, Base64.DEFAULT);
      messageDigest.reset();
      messageDigest.update(nbuf);
      messageDigest.update(pi.pin.getBytes("UTF-8"));
      byte dbuf[] = messageDigest.digest();
      pi.pindigest = Base64.encodeToString(dbuf, Base64.DEFAULT);
      msg.put(MessageUtils.KEY_PINDIGEST, pi.pindigest);
      
      msg.put(MessageUtils.KEY_ID, getDeviceId());
      msg.put(MessageUtils.KEY_NAME, service.getDeviceName());
      msg.put(MessageUtils.KEY_PORT, this.serverPort);
      Message m = new Message(Message.Type.MANAGEMENT, null, null, msg.toString());
      pi.pc.sendMessage(m);
      
      pi.state = PeerRequestState.STATE_PEER_REQ;
      pi.detail = "Pin is "+pi.pin;
      broadcastPeerState(pi);
    } catch (JSONException e) {
      // shouldn't happen!
      Log.e(TAG,"JSON error (shoulnd't be): "+e);
    } catch (UnsupportedEncodingException e) {
      // shouldn't happen!
      Log.e(TAG,"Unsupported encoding (shoulnd't be): "+e);
    }    
  }  
  /** called from checkMessage(pi) to handle message in state STATE_PEER_REQ */
  private boolean handlePeerReqResponse(PeerRequestInfo pi, Message m) {
    if (m.type!=Message.Type.MANAGEMENT) {
      failPeer(pi, "Received init_peer_req response of type "+m.type);
      return false;
    }
    String pinGuess = null;
    try {
      JSONObject msg = new JSONObject(m.body);
      String type = msg.getString(MessageUtils.KEY_TYPE);
      if (!MessageUtils.MSG_RESP_PEER_PIN.equals(type)) {
        if (MessageUtils.MSG_RESP_PEER_NOPIN.equals(type)) {
          return handlePeerNopin(pi, msg);
        }
        failPeer(pi, "Received init_peer_req response "+type);
        // TODO resp_peer_known
        return false;
      }
      // id, name, port, pin
      pinGuess = msg.getString(MessageUtils.KEY_PIN);
      pi.id = msg.getString(MessageUtils.KEY_ID);
      // TODO known IDs?
      int port = msg.getInt(MessageUtils.KEY_PORT);
      if (port!=pi.port)
        Log.w(TAG,"resp_peer_pin has different port: "+port+" vs "+pi.port);
      String name = msg.getString(MessageUtils.KEY_NAME);
      if (pi.instanceName==null)
        pi.instanceName = name;
      else if (!name.equals(pi.instanceName)) 
        Log.w(TAG,"resp_peer_pin has different name: "+name+" vs "+pi.instanceName);
    } catch (JSONException e) {
      failPeer(pi, "Error in resp_peer_pin message: "+e);
      return false;
    }
    Log.i(TAG,"Received resp_peer_pin in state peer_req with pin="+pinGuess);
    // pin?
    if (!pi.pin.equals(pinGuess)) {
      failPeer(pi, "Incorrect pin: "+pinGuess);
      return false;
    }
    // done
    try {
      JSONObject resp = new JSONObject();
      resp.put(MessageUtils.KEY_TYPE, MessageUtils.MSG_INIT_PEER_DONE);
      byte sbuf[] = new byte[8];
      protocol.getRandom(sbuf);
      pi.secret1 = Base64.encodeToString(sbuf, Base64.DEFAULT);
      resp.put(MessageUtils.KEY_SECRET, pi.secret1);
      resp.put(MessageUtils.KEY_PINNONCE, pi.pinnonce);
      JSONObject info = getInfo();
      if (info!=null)
        resp.put(MessageUtils.KEY_INFO, info);

      Message r = new Message(Message.Type.MANAGEMENT, null, null, resp.toString());
      pi.pc.sendMessage(r);
      
      pi.state = PeerRequestState.STATE_PEER_DONE;
      pi.detail = null;

      broadcastPeerState(pi);

      return true;
      
    } catch (JSONException e) {
      // shouldn't happen!
      Log.e(TAG,"JSON error (shoulnd't be): "+e);
    }    
    return false;
  }
  /** nopin response from peer in response to pin request */
  private synchronized boolean handlePeerNopin(PeerRequestInfo pi, JSONObject msg) {
    try {
      pi.id = msg.getString(MessageUtils.KEY_ID);
      // TODO known IDs?
      int port = msg.getInt(MessageUtils.KEY_PORT);
      if (port!=pi.port)
        Log.w(TAG,"resp_peer_nopin has different port: "+port+" vs "+pi.port);
      String name = msg.getString(MessageUtils.KEY_NAME);
      if (pi.instanceName==null)
        pi.instanceName = name;
      else if (!name.equals(pi.instanceName)) {
        Log.w(TAG,"resp_peer_nopin has different name: "+name+" vs "+pi.instanceName);
        pi.instanceName = name;
      }
      pi.secret2 = msg.getString(MessageUtils.KEY_SECRET);
      pi.peerInfo = msg.getJSONObject(MessageUtils.KEY_INFO);
    } catch (JSONException e) {
      failPeer(pi, "Error in resp_peer_pin message: "+e);
      return false;
    }
    Log.i(TAG,"Received resp_peer_nopin in state peer_req");

    pi.state = PeerRequestState.STATE_PEERED_UNTRUSTED;
    pi.detail = "no pin";

    peerRequests.remove(pi);
    Intent bi = new Intent(ACTION_PEER_REQUESTS_CHANGED);
    service.sendBroadcast(bi);
    broadcastPeerState(pi);
    
    addPeer(pi);
    
    return true;
  }
  private void addPeer(PeerRequestInfo preq) {
    // convert to PeerInfo
    PeerInfo pi = new PeerInfo();
    pi.createdTimestamp = System.currentTimeMillis();
    pi.info = preq.peerInfo;
    if (pi.info!=null) {
      try {
        if (pi.info.has(KEY_BTMAC))
          pi.btmac = pi.info.getString(KEY_BTMAC);
        if (pi.info.has(KEY_WIFIMAC))
          pi.wifimac = pi.info.getString(KEY_WIFIMAC);
        if (pi.info.has(KEY_IMEI))
          pi.imei = pi.info.getString(KEY_IMEI);
      }
      catch (JSONException e) {
        Log.e(TAG,"Unexpected JSON error unpacking peerInfo: "+e);
      }
    }
    pi.secret = protocol.combineSecrets(preq.secret1, preq.secret2);
    pi.ip = preq.src.getHostAddress();
    pi.ipTimestamp = System.currentTimeMillis();
    pi.port = preq.port;
    pi.portTimestamp = pi.ipTimestamp;
    pi.name = preq.instanceName;
    pi.id = preq.id;
    pi.trusted = preq.state==PeerRequestState.STATE_PEERED;
    pi.nickname = pi.name;
    pi.manual = preq.manual;
    
    pi.pc = preq.pc;
    if (pi.pc!=null)
      pi.pc.attach(pi);

    Log.i(TAG,"Converted PeerRequestInfo to PeerInfo");
    
    addPeer(pi);
  }
  /** get info to pass to peer */
  private synchronized JSONObject getInfo() {
    try {
      JSONObject info = new JSONObject();
      // has address?
      WifiInfo wifiinfo = wifi.getConnectionInfo();
      String wifimac = wifiinfo.getMacAddress();
      if (wifimac!=null)
        info.put(KEY_WIFIMAC, wifimac);
      // note: cannot get BluetoothAdapter from comms thread if not a looper
      String btmac = service.getBtMac();
      if(btmac!=null)  
        info.put(KEY_BTMAC, btmac);        
      String imei = service.getImei();
      if (imei!=null)
        info.put(KEY_IMEI, imei);
      return info;
    } catch (JSONException e) {
      // shouldn't happen!
      Log.e(TAG,"JSON error (shoulnd't be): "+e);
    }    
    return null;
  }
  private static int peerRequestNotificationId = 2;
  /** called on receipt of message after init_peer_done in peer */
  private synchronized boolean handlePeerDoneResponse(PeerRequestInfo pi, Message m) {
    if (m.type!=Message.Type.MANAGEMENT) {
      failPeer(pi, "Received init_peer_done response of type "+m.type);
      return false;
    }
    try {
      JSONObject msg = new JSONObject(m.body);
      String type = msg.getString(MessageUtils.KEY_TYPE);
      if (!MessageUtils.MSG_RESP_PEER_DONE.equals(type)) {
        failPeer(pi, "Received init_peer_done response "+type);
        return false;
      }
      pi.secret2 = msg.getString(MessageUtils.KEY_SECRET);
      pi.peerInfo = msg.getJSONObject(MessageUtils.KEY_INFO);
    } catch (JSONException e) {
      failPeer(pi, "Error in resp_peer_done message: "+e);
      return false;
    }
    // all done
    pi.state = PeerRequestState.STATE_PEERED;
    pi.detail = null;
    
    peerRequests.remove(pi);
    Intent bi = new Intent(ACTION_PEER_REQUESTS_CHANGED);
    service.sendBroadcast(bi);
    broadcastPeerState(pi);
    
    addPeer(pi);

    return true;
  }
  private String getDeviceId() {
    return service.getDeviceId();
  }
  /** clients */
  private ArrayList<ClientInfo> clients = new ArrayList<ClientInfo>();

  private PeerConnectionScheduler.Listener selectorListener = new PeerConnectionScheduler.Listener() {
    
    public void onAccept(PeerConnectionScheduler pcs,
        PeerConnection newPeerConnection) {
      Log.d(TAG,"onAccept new peer");
      // TODO Auto-generated method stub
      ClientInfo ci = new ClientInfo(newPeerConnection);
      newPeerConnection.setOnPeerConnectionListener(peerConnectionListener);
      newPeerConnection.attach(ci);
      clients.add(ci);
      Log.d(TAG,"Accepted new connection from "+newPeerConnection.getSocketChannel().socket().getInetAddress().getHostAddress()+":"+newPeerConnection.getSocketChannel().socket().getPort());      
    }
  };

  /** public API - peer request accept */
  public synchronized void acceptPeerRequest(Intent triggerIntent, String pin) {
    // TODO
    ClientInfo ci = getClientInfo(triggerIntent);
    if (ci==null) {
      Log.w(TAG,"Could not find ClientInfo for reject with id "+triggerIntent.getExtras().getString(EXTRA_ID));
      return;
    }
    ci.pin = pin;
    // notification?
    hideClientNotification(ci);
    // next step
    Message m = MessageUtils.getRespPeerPin(getDeviceId(), serverPort, service.getDeviceName(), pin);
    ci.pc.sendMessage(m);
    ci.state = ClientState.STATE_PEER_PIN;      
  }
  /** public API - peer request reject */
  public synchronized void rejectPeerRequest(Intent triggerIntent) {
    // TODO
    ClientInfo ci = getClientInfo(triggerIntent);
    if (ci==null) {
      Log.w(TAG,"Could not find ClientInfo for reject with id "+triggerIntent.getExtras().getString(EXTRA_ID));
      return;
    }
    // notification?
    hideClientNotification(ci);
    // reject
    protocol.removeClient(ci);
  }
  private synchronized ClientInfo getClientInfo(Intent triggerIntent) {
    String id = triggerIntent.getExtras().getString(EXTRA_ID);
    for (ClientInfo ci : clients) {
      if (ci.id.equals(id))
        return ci;
    }
    return null;
  }
  private synchronized void hideClientNotification(ClientInfo ci) {
    if (ci.notificationId!=0) {
      NotificationManager nm = (NotificationManager)service.getSystemService(Service.NOTIFICATION_SERVICE);
      nm.cancel(ci.notificationId);
      ci.notificationId = 0;
    }
  }
  /** public API - start peer add for specified host/port. Return PeerReq8estInfoActivity intent */
  public synchronized Intent addPeerRequest(InetAddress host, int port) {
    Intent i = new Intent(service, PeerRequestInfoActivity.class);
    i.putExtra(EXTRA_SOURCEIP, host.getHostAddress());
    i.putExtra(EXTRA_PORT, port);
    String name =  "Unknown (manually added)";
    i.putExtra(EXTRA_NAME, name);
    
    PeerRequestInfo pi = new PeerRequestInfo(name, host);
    pi.port = port;
    pi.state = PeerRequestState.STATE_SRV_FOUND;
    pi.manual = true;
    
    peerRequests.add(pi);
    
    Intent bi = new Intent(ACTION_PEER_REQUESTS_CHANGED);
    service.sendBroadcast(bi);
    updatePeer(pi);

    return i;
  }
  public synchronized List<PeerInfo> getPeers() {
    return PeersOpenHelper.getPeerInfos(database);
  }
  public synchronized List<PeerInfo> getEnabledPeers() {
    return PeersOpenHelper.getPeerInfos(database, PeersOpenHelper.KEY_ENABLED+" = 1", null);
  }
  public synchronized PeerInfo getPeer(String id) {
    PeerInfo pi = peerInfoCache.get(id);
    if (pi==null) {
      pi = PeersOpenHelper.getPeerInfo(database, id);
      if (pi!=null)
        peerInfoCache.put(id, pi);
    }
    return pi;
  }
  public synchronized void setPeerEnabled(PeerInfo peerInfo, boolean isChecked) {
    peerInfo.enabled = isChecked;
    PeerInfo pi = getPeer(peerInfo.id);
    pi.enabled = peerInfo.enabled;
    PeersOpenHelper.updatePeerInfo(database, pi);
    //NO peerInfoCache.put(pi.id, pi);
    
    Log.d(TAG,"setPeerEnabled id="+peerInfo.id+" -> "+isChecked);
    
    Intent i = new Intent(ACTION_PEER_STATE_CHANGED);
    i.putExtra(EXTRA_ID, pi.id);
    broadcastPeerState(pi);
  }
  private void broadcastPeerState(PeerInfo pi) {
    Intent i = new Intent(ACTION_PEER_STATE_CHANGED);
    i.putExtra(EXTRA_ID, pi.id);
    i.putExtra(EXTRA_NAME, pi.name);
    //Log.d(TAG,"sendBroadcast (peer state)...");
    service.sendBroadcast(i);
    //Log.d(TAG,"sendBroadcast done");
  }
  private class RemoteChannel extends NamedChannel {
    String peerId;
    String peerChannelName;
    public RemoteChannel(String peerId, String peerChannelName) {
      super("/"+peerId+"/"+peerChannelName);
      this.peerId = peerId;
      this.peerChannelName = peerChannelName;
    }
    @Override
    public synchronized void close() {
      // TODO Auto-generated method stub
      super.close();
    }
    @Override
    public synchronized JSONObject getImmediateValue() {
      // TODO Auto-generated method stub
      return super.getImmediateValue();
    }
    @Override
    protected void handleStart() {
      super.handleStart();
      PeerInfo pi = getPeer(peerId);
      if (pi==null) {
        Log.d(TAG,"No peer "+peerId+" in handleStart for "+peerChannelName);
      } else if (pi.pc==null) {
        Log.d(TAG,"No PeerConnection for "+peerId+" in handleStart for "+peerChannelName);
      } else {
        // send initial get
        JSONObject req = getRequest();
        JSONArray reqs = new JSONArray();
        reqs.put(req);
        Log.d(TAG,"Send initial request for "+name);
        pi.pc.sendMessage(new Message(Message.Type.REQUEST, "GET /ubihelper", null, reqs.toString()));
      }
      checkRemoteRequestTask(peerId);
    }
    @Override
    protected void handleStop() {
      super.handleStop();
      // TODO send end of get?      
      checkRemoteRequestTask(peerId);
    }    
    JSONObject getRequest() {
      JSONObject req = new JSONObject();
      try {
        req.put(KEY_NAME, name);
        req.put(KEY_PERIOD, this.period);
        req.put(KEY_TIMEOUT, DEFAULT_REMOTE_REQUEST_PERIOD*2);
      } catch (JSONException e) {
        // shouldn't
      }
      return req;
    }
    boolean isActive() { 
      return active;
    }
  }
  private class RemoteRequestTask extends TimerTask {
    private String peerId;
    RemoteRequestTask(String peerId) {
      this.peerId = peerId;
    }
    @Override
    public void run() {
      JSONArray reqs = new JSONArray();
      synchronized (PeerManager.this) {
        for (RemoteChannel rc : remoteChannels) {
          if (peerId.equals(rc.peerId) && rc.isActive())
            reqs.put(rc.getRequest());
        }
      }
      PeerInfo pi = getPeer(peerId);
      if (pi==null) {
        Log.d(TAG,"No peer "+peerId+" in RemoteRequestTask");
        return;
      }
      if (pi.pc==null) {
        Log.d(TAG,"No PeerConnection for "+peerId+" in RemoteRequestTask");
        return;
      }
      // send repeat get
      Log.d(TAG,"Send repeat request to "+peerId+": "+reqs.toString());
      pi.pc.sendMessage(new Message(Message.Type.REQUEST, "GET /ubihelper", null, reqs.toString()));
    }
  };
  private static final long DEFAULT_REMOTE_REQUEST_PERIOD = 10000;
  private HashMap<String,RemoteRequestTask> remoteRequestTasks = new HashMap<String,RemoteRequestTask>();
  private LinkedList<RemoteChannel> remoteChannels = new LinkedList<RemoteChannel>();
  private synchronized void checkRemoteRequestTask(String peerId) {
    boolean active= false;
    for (RemoteChannel rc : remoteChannels) {
      if (rc.peerId.equals(peerId) && rc.isActive()) {
        active = true;
        break;
      }
    }
    RemoteRequestTask remoteRequestTask = remoteRequestTasks.get(peerId);
    if (active && remoteRequestTask==null) {
      remoteRequestTask = new RemoteRequestTask(peerId);
      remoteRequestTasks.put(peerId, remoteRequestTask);
      remoteChannelTimer.scheduleAtFixedRate(remoteRequestTask, DEFAULT_REMOTE_REQUEST_PERIOD, DEFAULT_REMOTE_REQUEST_PERIOD);
    }
    else if (!active && remoteRequestTask!=null) {
      remoteRequestTask.cancel();
      remoteRequestTasks.remove(peerId);
    }
  }
  public synchronized NamedChannel getRemoteChannel(String peerId, String peerChannelName) {
    RemoteChannel rc = new RemoteChannel(peerId, peerChannelName);
    remoteChannels.add(rc);
    return rc;
  }
}




Java Source Code List

org.json.JSONArray.java
org.json.JSONException.java
org.json.JSONObject.java
org.json.JSONString.java
org.json.JSONStringer.java
org.json.JSONTokener.java
org.json.JSONWriter.java
uk.ac.horizon.ubihelper.channel.ChannelFactory.java
uk.ac.horizon.ubihelper.channel.ChannelListener.java
uk.ac.horizon.ubihelper.channel.ChannelManager.java
uk.ac.horizon.ubihelper.channel.ChannelValueEvent.java
uk.ac.horizon.ubihelper.channel.NamedChannel.java
uk.ac.horizon.ubihelper.channel.PullSubscription.java
uk.ac.horizon.ubihelper.channel.SharedVariableChannel.java
uk.ac.horizon.ubihelper.channel.Subscription.java
uk.ac.horizon.ubihelper.dns.DnsClient.java
uk.ac.horizon.ubihelper.dns.DnsProtocol.java
uk.ac.horizon.ubihelper.dns.DnsServer.java
uk.ac.horizon.ubihelper.dns.DnsUtils.java
uk.ac.horizon.ubihelper.httpserver.HttpClientHandler.java
uk.ac.horizon.ubihelper.httpserver.HttpContinuation.java
uk.ac.horizon.ubihelper.httpserver.HttpError.java
uk.ac.horizon.ubihelper.httpserver.HttpListener.java
uk.ac.horizon.ubihelper.j2se.Base64.java
uk.ac.horizon.ubihelper.j2se.Server.java
uk.ac.horizon.ubihelper.net.Fragment.java
uk.ac.horizon.ubihelper.net.Marshaller.java
uk.ac.horizon.ubihelper.net.Message.java
uk.ac.horizon.ubihelper.net.OnPeerConnectionListener.java
uk.ac.horizon.ubihelper.net.PeerConnectionScheduler.java
uk.ac.horizon.ubihelper.net.PeerConnection.java
uk.ac.horizon.ubihelper.protocol.ClientInfo.java
uk.ac.horizon.ubihelper.protocol.ClientState.java
uk.ac.horizon.ubihelper.protocol.MessageUtils.java
uk.ac.horizon.ubihelper.protocol.PeerInfo.java
uk.ac.horizon.ubihelper.protocol.ProtocolManager.java
uk.ac.horizon.ubihelper.service.BroadcastIntentSubscription.java
uk.ac.horizon.ubihelper.service.EnabledPeersChannel.java
uk.ac.horizon.ubihelper.service.LogManager.java
uk.ac.horizon.ubihelper.service.LogSubscription.java
uk.ac.horizon.ubihelper.service.PeerManager.java
uk.ac.horizon.ubihelper.service.PeersOpenHelper.java
uk.ac.horizon.ubihelper.service.Service.java
uk.ac.horizon.ubihelper.service.WifiDiscoveryManager.java
uk.ac.horizon.ubihelper.service.channel.BluetoothDiscoveryChannel.java
uk.ac.horizon.ubihelper.service.channel.CellLocationChannel.java
uk.ac.horizon.ubihelper.service.channel.CellStrengthChannel.java
uk.ac.horizon.ubihelper.service.channel.GpsStatusChannel.java
uk.ac.horizon.ubihelper.service.channel.LocationChannel.java
uk.ac.horizon.ubihelper.service.channel.MicChannel.java
uk.ac.horizon.ubihelper.service.channel.PollingChannel.java
uk.ac.horizon.ubihelper.service.channel.SensorChannel.java
uk.ac.horizon.ubihelper.service.channel.TimeChannel.java
uk.ac.horizon.ubihelper.service.channel.WifiScannerChannel.java
uk.ac.horizon.ubihelper.ui.AboutActivity.java
uk.ac.horizon.ubihelper.ui.ChannelListActivity.java
uk.ac.horizon.ubihelper.ui.ChannelPeerListActivity.java
uk.ac.horizon.ubihelper.ui.ChannelValueActivity.java
uk.ac.horizon.ubihelper.ui.ChannelViewActivity.java
uk.ac.horizon.ubihelper.ui.LoggingChannelListActivity.java
uk.ac.horizon.ubihelper.ui.LoggingPreferences.java
uk.ac.horizon.ubihelper.ui.MainPreferences.java
uk.ac.horizon.ubihelper.ui.ManagePeersActivity.java
uk.ac.horizon.ubihelper.ui.PeerInfoActivity.java
uk.ac.horizon.ubihelper.ui.PeerManualAddActivity.java
uk.ac.horizon.ubihelper.ui.PeerRequestActivity.java
uk.ac.horizon.ubihelper.ui.PeerRequestInfoActivity.java
uk.ac.horizon.ubihelper.ui.SearchPeersActivity.java
uk.ac.horizon.ubihelper.ui.TestActivity.java
uk.ac.horizon.ubihelper.ui.WifiStatusActivity.java
uk.ac.horizon.ubihelper.websocket.ClientWebsocket.java
uk.ac.horizon.ubihelper.websocket.ReadyState.java
uk.ac.horizon.ubihelper.websocket.SocketChannelWebsocket.java
uk.ac.horizon.ubihelper.websocket.WebsocketListener.java
uk.ac.horizon.ubihelper.websocket.Websocket.java