Brillo: Android客户端开发 - 查找服务,调用API

在进行Brillo系统开发与学习的时候,有一个客户端程序可以很好地避免在命令行中输入繁琐的命令,进行设备的配对,获取access token会变得异常的简单。

  • 使NdsManager发现局域网中的Brillo设备

Brillo设备上运行着avahi服务,这就意味着Android设备上的Nsd服务可以发现在同一个局域网中的Brillo设备。

相关的文档可以参考:

  1. http://developer.android.com/reference/android/net/nsd/NsdManager.html
  2. http://developer.android.com/training/connect-devices-wirelessly/nsd.html

相关的参考代码在:

development/sample/training/NsdChat

在Activity的onResume()中调用NdsManager.discoverServices(),去发现_privet._tcp服务;在onPause()中调用NdsManager.stopServiceDiscovery(); 当有相关的服务时,会回调NsdManager.DiscoveryListener.onServiceFound(), 这时候我们还需要通过NsdManager.resolveService()去获取ip地址(InetAddress)和端口号。当服务停止运行时,会回调NsdManager.DiscoveryListener.onServiceLost(),这时候也可以通过NsdManager.resolveService()去查找丢失服务的主机的ip地址,将列表中的那一项删除。

这时发现Brillo系统中的avahi service只发布了80端口的服务, secure http的端口号可以通过/privet/info查找。

  • 建立https client访问设备

由于 Brillo系统中的web server使用的是自签名证书,所以在建立连接,进行访问的时候,要忽略证书。所以建立https连接会相对复杂一些。

使用HttpURLConnection来获取access token:

    public static void deviceInfo(String path, OnMessage callback) {
        try {
            URL url = new URL(path + "/privet/info");
            HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
            urlConnection.setRequestProperty("Authorization", "Basic anonymous");
            urlConnection.setRequestProperty("Content-Type", "application/json");
            urlConnection.connect();
            try {
                InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                BufferedReader bufReader = new BufferedReader(new InputStreamReader(in));
                StringBuilder sb = new StringBuilder();
                for (String line = bufReader.readLine(); line != null;) {
                    sb.append(line).append("\n");
                    line = bufReader.readLine();
                }

                callback.onMessage(sb.toString(), null);
                return;
            } finally {
                urlConnection.disconnect();
            }
        } catch (MalformedURLException muex) {
            Log.e(TAG, "Malformed url: " + path);
            callback.onMessage(null, muex.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            callback.onMessage(null, e.getMessage());
        }
    }

Android官网推荐使用HttpURLConnection来建立连接:

http://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-apache-http-client

由于建立https连接需要处理服务器证书的问题,所以建立https连接还是使用apache http client:

private static class MySSLSocketFactory extends SSLSocketFactory {
    private SSLContext mSslContext;
    private Callback mCallback;

    public MySSLSocketFactory(KeyStore truststore, Callback callback) throws NoSuchAlgorithmException,
            KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);
        mCallback = callback;

        TrustManager tm = new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chains, String authType)
                    throws CertificateException {
                if (chains == null || mCallback == null) {
                    return;
                }

                mCallback.onCertAvailable(chains, authType);
            }
        };

        mSslContext = SSLContext.getInstance("TLS");
        mSslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
                throws IOException, UnknownHostException {
        Log.v(TAG, " -> Create socket: socket=" + socket + ", host=" + host + ", port=" + port + ", autoClose=" + autoClose);
        return mSslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return mSslContext.getSocketFactory().createSocket();
    }
}

public static synchronized HttpClient getHttpClient(int port, int securePort, Callback callback) {
    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        SSLSocketFactory factory = new MySSLSocketFactory(trustStore, callback);
        factory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);

        ConnManagerParams.setTimeout(params, 10000);
        HttpConnectionParams.setConnectionTimeout(params, 10000);
        HttpConnectionParams.setSoTimeout(params, 100000);

        SchemeRegistry reg = new SchemeRegistry();
        reg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), port));
        reg.register(new Scheme("https", factory, securePort));

        ClientConnectionManager connManager = new ThreadSafeClientConnManager(params, reg);

        return new DefaultHttpClient(connManager, params);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return new DefaultHttpClient();
}

相关的文档可以参考http://android-developers.blogspot.com/2011/09/androids-http-clients.html

  • 功能与界面

可以看到在主界上NSD会一直偿试发现同一个局域网中的Brillo设备,并将找到的设备显示在下面的列表中。当然,如果Brillo设备不想广播自己的信息,你也可通过输入host name/ip address和端口号的形式来访问设备信息(当前只支持http):

2016_02_20_brobdm_discovery

在设备界面会显示当前所要访问的设备的IP地址和端口号。同时提供一组的菜单:

  1. INFO: /privet/info: 显示设备信息
  2. CERT:: 显示设备web server(https)使用的证书
  3. PAIRING START: /privet/v3/pairing/start:进行设备配对,以获取access token
  4. PAIRING CONFIRM: /privet/v3/pairing/confirm:确认配对
  5. PAIRING CANCEL: /privet/v3/pairing/cancel:取消配对
  6. AUTH: /privet/v3/auth:获取Owner的access token

2016_02_20_brobdm_main

在获取设备信息后,我们就可以知道http/https的端号了,之后的连接都是通过https访问:

2016_02_20_brobdm_info

系统证书信息:

2016_02_20_brobdm_cert

开始进行配对的操作:

2016_02_20_brobdm_pairing_start

确认配对信息:

2016_02_20_brobdm_pairing_confirm

获取access token:

2016_02_20_brobdm_auth

  • 相关代码

代码已经上传到github.com上:https://github.com/brobwind/BroDm ->@<-, 可以通过git命令下载:

$ git clone https://github.com/brobwind/BroDm

由于有native 代码并且还需要使用到Brillo系统的代码,编译起来会相对复杂一些。编译使用的是在Android项目中编译,如果熟悉Android项目编译的话,会相对容易很多。

编译:

## Rebuild jni/libs/librokit.a
	$ mkdir -p /local/brillo-m9-dev && cd /local/brillo-m9-dev
	$ repo init -u https://android.googlesource.com/brillo/manifest -b brillo-m9-dev
	$ repo sync
	$ . build/envsetup.sh
	$ lunch brilloemulator_arm-eng
	$ mkdir -pv packages/apps && cd packages/apps
	$ git clone https://github.com/brobwind/BroDm.git
	$ cd BroDm/jni/libs && mma -j 8

 After all the jni/libs/libbrokit.a will be updated

## Build BroDm APK
	$ mkdir -pv /local/android-5.1.1_r15 && cd /local/android-5.1.1_r15
	$ repo init -u https://android.googlesource.com/platform/manifest -b android-5.1.1_r15
	$ repo sync
	$ . build/envsetup.sh
	$ lunch aosp_arm-eng
	$ cd packages/apps
	$ git clone https://github.com/brobwind/BroDm.git
	$ cd BroDm && mma -j 8

 After all the APK will be installed in:
	/loca/android-5.1.1_r15/out/target/product/generic/data/app/BroDm/BroDm.apk
  • 通过WEB API执行相关的命令,如控制LED

传送门: ->@<-

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注