顯示具有 android 標籤的文章。 顯示所有文章
顯示具有 android 標籤的文章。 顯示所有文章

2015年12月14日 星期一

Utilize Avahi Libraries in Android for mDNS Discovering



零.

Continue my previous avahi article, here, i would port the same example code to Android platform.

Download and unravel Android NDK

for me, the untarred NDK root locates in ~/Android/AndroidNDK/android-ndk-r10e
  
And install the android tools in the linux system. (yum for fedora, apt-get for ubunu/debian)

sudo apt-get install gcc-arm-linux-androideabi


sudo apt-get install android-tools-adb


I strongly suggest you DO NOT install the others android-tool, headers and libraries via apt-get/yum.

If you need those, just download from Android website and decompress them.


一.

      Download and untar the avahi libraries, in here I use version 0.6.31.

      Download 2 patches and place them in the untarred avahi folder:

0001-Add-Android-support.patch
0002-Add-uninstalled.pc.in-files.patch

apply the patches.

patch -p1  0001-Add-Android-support.patch

      You would encounter a warning here, just ignore it. it is for there is no .git folder inside the avahi folder.


patch -p1 0002-Add-uninstalled.pc.in-files.patch


     For the old configuration tools does not support androideabi, you need to upgrade the config.guess and config.sub from here. it is, to overwrite these 2 files inside the avahi root folder.


二.
    The struct in6_pktinfo has been added in Android 4.0(2011/10). When the avahi 0.6.31 was released (2012/2), the BioniC (the position of glibC for Android) libraries on most populace's Android phone was not implemented the whole IVP6. Therefore, the structure ha been declare inside avahi. In present(2015/12), I thought there is no one set build-target version before 4.0.   It is requisite to modify code to remove the obsolete lines in avahi-core/socket.c , about line 67:


#endif

#if(0)
#ifdef __BIONIC__
struct in6_pktinfo {
        struct in6_addr ipi6_addr;
        int ipi6_ifindex;
};
#endif
#endif

static void mdns_mcast_group_ipv4(struct sockaddr_in *ret_sa) {

The in6_pktinfo has been declared in NDKfolder/platforms/android-version/arch/usr/include/linux/ipv6.h, to comment out it to avoid compilation error.

Optional:
if your application would be integrated  web-browser and avahi disovering functions, you should modify the code avahi-core/netlink.c, about line 160:


    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = groups;
#if(1)
    addr.nl_pid = 0;
#else 
    addr.nl_pid = getpid();
#endif

    if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {

It is, the add.nl_pid value would not be conflicted with browser's. More detail of this change is in here.

三.
 The Avahi configure arguments for me be:browser

./configure --prefix=$PWD/built --disable-dbus --disable-mono --disable-monodoc --disable-qt4 --disable-qt3 --disable-glib --disable-gobject --disable-gtk --disable-gtk3 --disable-gdbm --disable-dbm --disable-python --disable-pygtk -disable-libdaemon --disable-nls --disable-xmltoman --with-xml=none --disable-core-docs --disable-doxygen-doc CPPFLAGS="-I/home/gaiger/Android/AndroidNDK/android-ndk-r10e/platforms/android-14/arch-arm/usr/include -mcpu=arm926ej-s  -mfloat-abi=soft" LDFLAGS=-L/home/gaiger/Android/AndroidNDK/android-ndk-r10e/platforms/android-14/arch-arm/usr/lib --host=arm-linux-androideabi --disable-stack-protector  --with-distro=debian

 NOTE :
     Zero:
      I disable most functions of Avahi. Because those are useless for the goal of discovering mDNS service and dependent on dbus, gtk, qt and so forth libraries.  To building those depended libraries are difficult for Android.

     One:
     The path, /home/gaiger/Android/AndroidNDK/android-ndk-r10e/platforms/android-14/arch-arm/usr, is where the Android system libraries locates, those are being part of Android NDK.

         /home/gaiger/Android/AndroidNDK/android-ndk-r10e :  where AndroidNDK root locates.
        platforms/android-14/arch-arm  : I chose to  android 4.0 with arm-architecture as my target system.

    it is very natural to add include path (-Iusr_path/include) and linking path(-Iusr_path/lib) as arguments in cross-compilation.

    TWO:
     -mcpu=arm926ej-s  -mfloat-abi=softfp :  To avoid crash in some (mostly those are not existed ) cheap Android device CPU without hardware floating computing. Besides,  Avahi do not involve in heavy floating point computing, So adapt pure soft-float computing would not lead mDNS discovering sluggish.
      arm926ej-s means, assuming the CPU does not support hardware floating but supports Jazelle ( for java VM). 
      More detail of CPU architecture, you could refer to this article.

   THREE :
    --with-distro=debian : for me , I use ubuntu.  If you use fedora/CentOS, you use set it as fedora.

   FOUR:
  --disable-stack-protector : there is no libssp (stack smash protector) in current BioniC defaultly. To advert configuring error, one should disable this function. The detail about libssp is in here.

四.
  After configuring done, it is very instinctive to press make and make install.
  To this step, the avahi libraries for Android has been built, in the avahi_root/built


五.
   Create a folder and  a sub-folder in avahi root: sandyboxcode/jni

   Copy the code sandbox-discover-standalone.c of my last article in folder sandyboxcode/jni.
   And Create two files, Application.mk and Android.mk in the same folder ( sandyboxcode/jni).

The content of file Application.mk be:


#APP_ABI := armeabi-v7a
APP_ABI := armeabi

     
The Android.mk file be:


LOCAL_PATH := $(my-dir)

ifneq ($(DYNAMIC_LINK),true)
DYNAMIC_LINK = false

include $(CLEAR_VARS)
LOCAL_MODULE    := libavahi-core
LOCAL_SRC_FILES := ../../built/lib/libavahi-core.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := libavahi-common
LOCAL_SRC_FILES := ../../built/lib/libavahi-common.a
include $(PREBUILT_STATIC_LIBRARY)

else

include $(CLEAR_VARS)
LOCAL_MODULE    := libavahi-core
LOCAL_SRC_FILES := ../../built/lib/libavahi-core.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE    := libavahi-common
LOCAL_SRC_FILES := ../../built/lib/libavahi-common.so
include $(PREBUILT_SHARED_LIBRARY)

endif #if $DYNAMIC_LINK .neq. true


include $(CLEAR_VARS)
LOCAL_MODULE := sandbox-discover-standalone
LOCAL_SRC_FILES := sandbox-discover-standalone.c
 
LOCAL_CFLAGS := -O2 -mcpu=arm926ej-s -mfloat-abi=softfp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../built/include 


ifeq ($(DYNAMIC_LINK),false)
LOCAL_STATIC_LIBRARIES := libavahi-core libavahi-common
else
LOCAL_SHARED_LIBRARIES += libavahi-common libavahi-core
endif #if $(DYNAMIC_LINK) eq false

LOCAL_LDLIBS +=  -llog
include $(BUILD_EXECUTABLE)


Then to the sandbox folder, type :


$your_ndk_path/ndk-build -B V=1


Now you have gotten binary of sandbox-discover-standalone for Android, under the folder libs/armeabi .


六.

  Push the built binary to a android device, which might need to be rooted to execute a binary in shell.

  For me, I use the development board tiny210 on Android 4.0, which was rooted natively.





check if the device connected to working computer indeed:

$ adb devices
List of devices attached 
0123456789ABCDEF device


push the binary to the android :

$ adb push libs/armeabi/sandbox-discover-standalone sandbox-discover-standalone
2291 KB/s (202300 bytes in 0.086s)


execute it in the shell:

$ adb shell 
/ # chmod 777 sandbox-discover-standalone
/ # ./sandbox-discover-standalone 
1 services have been found

Service Type: _http._tcp
Service Name: GAIGER NETEYE CS2230
Domain Name: local
Interface: eth0 IPv4
Address: 192.168.13.107/NETEYECS2230-001A97018882.local:80
TEXT Data: NULL

discover cast 1002 msec


It is successful totally.

Known insufficiency :

if you encounter  Segmentation fault when you execute the binary in Android,
you should correct the code in sandbox-discover-standalone.c, of function ServiceResolverCallback:


 pServiceInfo->pServiceName = strndup(pServiceName, MAX_STR_LEN);
 pServiceInfo->pDomainName = strndup(pDomain, MAX_STR_LEN);
#ifndef __ANDROID__ 
 pServiceInfo->pInterface = strndup(if_indextoname(interface, (char*)pServiceName), MAX_STR_LEN);
#endif 
 pServiceInfo->pProtocolNumber = strndup(avahi_proto_to_string(protocol), MAX_STR_LEN);

It could avoid the crashing: the function if_indextoname of bionic libraries is not as stable as libc.


Extra:

It is passible to write a stand Makefile for Android ndk build :

i assure the Makefile be in sandyboxcode/Makefile (NOT inside the jni folder), for the sandbox-discover-sandalone.c be :


CC := arm-linux-androideabi-gcc
CXX := arm-linux-androideabi-g++

INC := -I~/Android/AndroidNDK/android-ndk-r10e/platforms/android-14/arch-arm/usr/include 

INC += -I../built/include -Ijni

AVAHI_LIB_PATH := ../built/lib

LIB := ~/Android/AndroidNDK/android-ndk-r10e/platforms/android-14/arch-arm/usr/lib/liblog.so

LIB += $(AVAHI_LIB_PATH)/libavahi-core.a
LIB += $(AVAHI_LIB_PATH)/libavahi-common.a

CFLAGS := -g 

all :
 $(CC) $(CFLAGS) $(INC)  jni/sandbox-discover-standalone.c $(LIB) -o sandbox-discover-standalone
clean :
 rm sandbox-discover-standalone -rf

Note : the shared libraries liblog.so be linking directly!.

2015年5月25日 星期一

Android as Bluetooth Low Energy Peripheral (GATT server).



  I demonstrate how to write a simple BLE peripheral application in Android here. I am bad in Android development, The UI would be very ugly, but the code work:

  Currently(5/25/2015), the code could be running in Nexus 6 or Nexus 9 only based on my test. The other phones or tablets DO NOT support to be a BLE peripheral.  So, if you really interested in the issue of Android as BLE Peripheral , please open your wallet or swipe your card, to buy a GOOGLE official device, thank you.


 To add a characteristic as notification is little bit complicated, In here I just add read and write characteristics.
  About the notification, I put code in the last part of this post.

You should add

 <uses-permission android:name="android.permission.BLUETOOTH" />
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

The 2 lines in your AndroidManifest.xml, like this :


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gaiger.simplebleperipheral"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />    

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

The kernal code are below , note the AdvertiseCallback is callback of BluetoothLeAdvertiser ::startAdvertising, and BluetoothGattServerCallback is callback function of ALL BluetoothGattCharacteristic.


BLEPeripheral.java: (that is what you want)


package com.gaiger.simplebleperipheral;


import java.util.List;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.util.Log;


public class BLEPeripheral{

 public interface ConnectionCallback {
     void onConnectionStateChange(BluetoothDevice device, int newState);
 }

 
 BluetoothManager mManager;
 BluetoothAdapter mAdapter;
 
 BluetoothLeAdvertiser  mLeAdvertiser;
 
 AdvertiseSettings.Builder settingBuilder;
 AdvertiseData.Builder advBuilder;        
 
 BluetoothGattServer  mGattServer;  
 
 ConnectionCallback mConnectionCallback;
 
 public interface WriteCallback {
     void onWrite(byte[] data);
 }

 WriteCallback mWriteCallback;
 
 public static boolean isEnableBluetooth(){
  return BluetoothAdapter.getDefaultAdapter().isEnabled();
 }
 
 public int init(Context context){
  
  if(null == mManager)
  {
   mManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
  
   if(null == mManager)
    return -1;
  
   if(false == context.getPackageManager().
     hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
    return -2;          
  }      
  
  if(null == mAdapter)
  {
   mAdapter = mManager.getAdapter();
  
   if(false == mAdapter.isMultipleAdvertisementSupported())
    return -3; 
  }
  
  if(null == settingBuilder)
  {
   settingBuilder = new AdvertiseSettings.Builder();
   settingBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
   settingBuilder.setConnectable(true);   
   settingBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);  
  } 
  
  if(null == advBuilder)
  {
   advBuilder = new AdvertiseData.Builder();                                         
   mAdapter.setName("SimplePeripheral");        
   advBuilder.setIncludeDeviceName(true);
  }
  
  
        if(null == mGattServer)
        {
         mGattServer = mManager.openGattServer(context, mGattServerCallback);
        
         if(null == mGattServer)
          return -4;        
     
         addDeviceInfoService();              
        }
        
  return 0;
 }
 
 public void setConnectionCallback(ConnectionCallback callback)
 {
  mConnectionCallback = callback;
 }
 
 public void close()
 {
  if(null != mLeAdvertiser)
   stopAdvertise();
  
  if(null != mGattServer)
   mGattServer.close();
  mGattServer = null;
  
  if(null != advBuilder)
   advBuilder = null;
  
  if(null != settingBuilder)
        settingBuilder = null;
  
  if(null != mAdapter)
        mAdapter = null;
  
  if(null != mManager)
        mManager = null;   
 }
 

 public static String getAddress(){return BluetoothAdapter.getDefaultAdapter().getAddress();}
 
 private AdvertiseCallback mAdvCallback = new AdvertiseCallback() {
  
  @Override
  public void onStartFailure(int errorCode){
   Log.d("advertise","onStartFailure");
  }
  
  @Override
  public void onStartSuccess(AdvertiseSettings settingsInEffect){
   Log.d("advertise","onStartSuccess");
  };
 };
 
  private final BluetoothGattServerCallback mGattServerCallback 
   = new BluetoothGattServerCallback(){
   
  @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState){
   Log.d("GattServer", "Our gatt server connection state changed, new state ");
         Log.d("GattServer", Integer.toString(newState));      
                  
         if(null != mConnectionCallback && BluetoothGatt.GATT_SUCCESS == status)
          mConnectionCallback.onConnectionStateChange(device, newState);
          
            super.onConnectionStateChange(device, status, newState);
        }
   
  @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            Log.d("GattServer", "Our gatt server service was added.");
            super.onServiceAdded(status, service);
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            Log.d("GattServer", "Our gatt characteristic was read.");
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 
              characteristic.getValue());
        }

        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d("GattServer", "We have received a write request for one of our hosted characteristics");
            //Log.d("GattServer", "data = "+ value.toString()); 
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            
            if(null != mWriteCallback)
             mWriteCallback.onWrite(value);
            
        }

        @Override
        public void onNotificationSent(BluetoothDevice device, int status)
        {
         Log.d("GattServer", "onNotificationSent");          
         super.onNotificationSent(device, status);                  
        }
        
        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            Log.d("GattServer", "Our gatt server descriptor was read.");
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            
        }

        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d("GattServer", "Our gatt server descriptor was written.");
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
        }

        @Override
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
            Log.d("GattServer", "Our gatt server on execute write.");
            super.onExecuteWrite(device, requestId, execute);
        }
   
 };
 
 private void addDeviceInfoService()
 {  
  if(null == mGattServer)
   return;  
  
  final String SERVICE_DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb";
        final String SOFTWARE_REVISION_STRING = "00002A28-0000-1000-8000-00805f9b34fb";
        
        
  BluetoothGattService previousService =
           mGattServer.getService( UUID.fromString(SERVICE_DEVICE_INFORMATION));
                 
     if(null != previousService)        
         mGattServer.removeService(previousService);
      
        
        BluetoothGattCharacteristic softwareVerCharacteristic = new BluetoothGattCharacteristic(
          UUID.fromString(SOFTWARE_REVISION_STRING), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );
        
        BluetoothGattService deviceInfoService = new BluetoothGattService(
          UUID.fromString(SERVICE_DEVICE_INFORMATION), 
          BluetoothGattService.SERVICE_TYPE_PRIMARY);
        
        
        softwareVerCharacteristic.setValue(new String("0.0.0").getBytes());
        
        deviceInfoService.addCharacteristic(softwareVerCharacteristic);
        mGattServer.addService(deviceInfoService);
 }
 
 
 
 
 public void setService(String read1Data, String read2Data, WriteCallback
   writeCallBack)
 {  
         
  if(null == mGattServer)
   return ;
  
  stopAdvertise();
  
        final String  SERVICE_A = "0000fff0-0000-1000-8000-00805f9b34fb";
        final String  CHAR_READ1 = "0000fff1-0000-1000-8000-00805f9b34fb";
        final String  CHAR_READ2 = "0000fff2-0000-1000-8000-00805f9b34fb";
        final String  CHAR_WRITE = "0000fff3-0000-1000-8000-00805f9b34fb";       
        final String  CHAR_NOTIFY = "0000fff4-0000-1000-8000-00805f9b34fb";       
        
        
        
        BluetoothGattService previousService =
          mGattServer.getService( UUID.fromString(SERVICE_A));
                
     if(null != previousService)        
         mGattServer.removeService(previousService);
        
               
        
        BluetoothGattCharacteristic read1Characteristic = new BluetoothGattCharacteristic(
          UUID.fromString(CHAR_READ1), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );                 
        
        BluetoothGattCharacteristic read2Characteristic = new BluetoothGattCharacteristic(
          UUID.fromString(CHAR_READ2), 
          BluetoothGattCharacteristic.PROPERTY_READ,
          BluetoothGattCharacteristic.PERMISSION_READ
          );
                
        BluetoothGattCharacteristic writeCharacteristic = new BluetoothGattCharacteristic(
             UUID.fromString(CHAR_WRITE), 
             BluetoothGattCharacteristic.PROPERTY_WRITE,
             BluetoothGattCharacteristic.PERMISSION_WRITE
             );
        
       
       
                               
        read1Characteristic.setValue(read1Data.getBytes());
        read2Characteristic.setValue(read2Data.getBytes());
        mWriteCallback = writeCallBack;
       
        
        BluetoothGattService AService = new BluetoothGattService(
          UUID.fromString(SERVICE_A), 
          BluetoothGattService.SERVICE_TYPE_PRIMARY);
                         
        
        AService.addCharacteristic(read1Characteristic);
        AService.addCharacteristic(read2Characteristic);
        AService.addCharacteristic(writeCharacteristic); 
        
        

        final BluetoothGattCharacteristic notifyCharacteristic = new BluetoothGattCharacteristic(
             UUID.fromString(CHAR_NOTIFY), 
             BluetoothGattCharacteristic.PROPERTY_NOTIFY,
             BluetoothGattCharacteristic.PERMISSION_READ
             );
           
        
        notifyCharacteristic.setValue(new String("0"));
        AService.addCharacteristic(notifyCharacteristic);  
        
        final Handler handler = new Handler();
        
        Thread thread = new Thread() {
         int i = 0;         
         
            @Override
            public void run() {             
               
                    while(true) {
                     
                        try {
       sleep(1500);
      } catch (InterruptedException e) {}
                        
                        handler.post(this);
                        
                        List<BluetoothDevice> connectedDevices 
                         = mManager.getConnectedDevices(BluetoothProfile.GATT);
                        
                        if(null != connectedDevices)
                        {
                         notifyCharacteristic.setValue(String.valueOf(i).getBytes());
                                                 
                         if(0 != connectedDevices.size())
                          mGattServer.notifyCharacteristicChanged(connectedDevices.get(0),
                            notifyCharacteristic, false);
                        }                       
                        i++;
                    }               
            }
        };

        thread.start();
           
        
        mGattServer.addService(AService);
 }
  
 
 public void startAdvertise(String scanRespenseName)
 {  
  mAdapter.setName(scanRespenseName);        
        advBuilder.setIncludeDeviceName(true);
        
        startAdvertise();
 }
 
 public void startAdvertise()
 {
  if(null == mAdapter)
   return;
  
  if (null == mLeAdvertiser) 
   mLeAdvertiser = mAdapter.getBluetoothLeAdvertiser();
        
  if(null == mLeAdvertiser)
   return;
                     
         mLeAdvertiser.startAdvertising(settingBuilder.build(), 
           advBuilder.build(), mAdvCallback);         
 }
 
 public void stopAdvertise()
 {
  if(null != mLeAdvertiser)
   mLeAdvertiser.stopAdvertising(mAdvCallback);
  
  mLeAdvertiser = null;  
 }
  
}


There is a callback interface WriteCallback to hold tthe data which the BLE has written.

MainActivity.java : (UI part)


package com.gaiger.simplebleperipheral;

import java.io.UnsupportedEncodingException;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends Activity {

 private BLEPeripheral blePeri; 
 private CheckBox  adverstiseCheckBox;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        adverstiseCheckBox = (CheckBox) findViewById(R.id.advertise_checkBox);
        
        blePeri = new BLEPeripheral();                        
        
        adverstiseCheckBox.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
             
             if(true == adverstiseCheckBox.isChecked())
             {              
            
              TextView textView;
              textView = (TextView)findViewById(R.id.status_textView);
              textView.setText("advertising");              
              
              blePeri.setService(new String("GAIGER"),
                new String("AndroidBLE"),
                mWrittenCallback
                );    
              
              blePeri.startAdvertise();
             }
             else
             {
              TextView textView;
              textView = (TextView)findViewById(R.id.status_text);
              textView.setText("disable");
              blePeri.stopAdvertise();              
             }
            }
        });
        
        adverstiseCheckBox.setEnabled(false);
        
     if(false == BLEPeripheral.isEnableBluetooth())
     {
      
      Intent intentBtEnabled = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 
            // The REQUEST_ENABLE_BT constant passed to startActivityForResult() is a locally defined integer (which must be greater than 0), that the system passes back to you in your onActivityResult() 
            // implementation as the requestCode parameter. 
            int REQUEST_ENABLE_BT = 1;
            startActivityForResult(intentBtEnabled, REQUEST_ENABLE_BT);
            
            Toast.makeText(this, "Please enable bluetooth and execute the application agagin.",
     Toast.LENGTH_LONG).show();
     }          
     
    }

    
    byte[]  writtenByte;
    BLEPeripheral.WriteCallback mWrittenCallback = new BLEPeripheral.WriteCallback()
    {
      @Override
  public        
      void onWrite(byte[] data)
      {
       writtenByte = data.clone();
      
       Thread timer = new Thread(){
              public void run() {
                  
                  runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                     TextView textView;
                     textView = (TextView)findViewById(R.id.written_textView);
                  try {
          textView.setText(new String(writtenByte, "UTF-8"));
         } catch (UnsupportedEncodingException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
         }
                   }
                      });
              }
              
          };
          
          timer.start();
       
       
        }            
          
   };
    
   
 
    
    Runnable mCleanTextRunnable = new Runnable() {
        public void run() {
         TextView textView;
       textView = (TextView)findViewById(R.id.connected_textView);
       textView.setText("no connection");             
        }
    };
    
    Handler mConnectTextHandler = new Handler() {  
        
        @Override                  
        public void handleMessage(Message msg) {  
            super.handleMessage(msg);
            String data = (String)msg.obj;  
            
            
            switch (msg.what) {
            
            case 0:
             data += new String(" disconnected");
             this.postDelayed(mCleanTextRunnable, 3000);
             break;
             
            case 2: 
             data += new String(" connected");                                                          
                break;                  
            default:  
                break;  
            }
            
            TextView textView;
      textView = (TextView)findViewById(R.id.connected_textView);
      textView.setText(data);              
        }  
  
    }; 
    
    
    @Override
    public void onResume(){
     
        super.onResume();
                       
        int sts;
        sts = blePeri.init(this);
       
//        blePeri.mConnectionCallback = new BLEPeripheral.ConnectionCallback (){
//         @Override
//      public void onConnectionStateChange(BluetoothDevice device, int newState){
//       Log.d("main","onConnectionStateChange");
//      }
//        };
//       
        blePeri.setConnectionCallback( new BLEPeripheral.ConnectionCallback (){
             @Override
          public void onConnectionStateChange(BluetoothDevice device, int newState){           
                                          
              Message msg = new Message();    
              
              msg.what = newState;                      
              msg.obj = new String( device.getName() +" "+ device.getAddress() );
              
              mConnectTextHandler.sendMessage(msg);                                       
          }
            }
           
         );
        
         
        
        if(0  > sts)
     {
      if(-1 == sts)
       Toast.makeText(this, "this device is without bluetooth module",
         Toast.LENGTH_LONG).show();
      
      if(-2 == sts)
       Toast.makeText(this, "this device do not support Bluetooth low energy", 
         Toast.LENGTH_LONG).show();
      
      if(-3 == sts)
       Toast.makeText(this, "this device do not support to be a BLE peripheral, " +
         "please buy nexus 6 or 9 then try again", 
         Toast.LENGTH_LONG).show();
      
      finish();
     }   
        
        TextView textView;
     textView = (TextView)findViewById(R.id.mac_textView);
     
     textView.setText(BLEPeripheral.getAddress());
     
     
     adverstiseCheckBox.setEnabled(true);            
          
     
    }
    
    
    
    @Override
    protected void onStop() {
        super.onStop();      
    }

        
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

activity_main.xml: (layout, very ugly)


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.awind.presentsenseperipheral.MainActivity" >

    <TextView
        android:id="@+id/mac_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/status_text"
        android:layout_marginLeft="18dp"
        android:layout_toRightOf="@+id/status_text"
        android:text="00:11:22:AA:BB:CC"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <CheckBox
        android:id="@+id/advertise_checkBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/mac_text"
        android:layout_marginLeft="34dp"
        android:layout_marginTop="41dp"
        android:text="Advertise" />

    <TextView
        android:id="@+id/status_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/advertise_checkBox"
        android:layout_alignParentTop="true"
        android:layout_marginTop="124dp"
        android:text="Disable"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/connected_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/mac_text"
        android:layout_centerVertical="true"
        android:text="no connection"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/written_textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/mac_text"
        android:layout_alignParentBottom="true"
        android:text="WrittenText"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>


    I do not like to write too much explanation in here, One said: if you could implement, you understand it; if you could not, you know about nothing of it .

    Notice the part of notifyCharacteristic ,I create a thread, which updates value and send a signal to BluetoothGattServer, to inform the BLE central the value has changed.

  There is a bug in written text update: once WriteCallback::onWrite been called, the runOnUiThread for updating  R.id.written_textView should be executed once. But in my code, the update would not work. That is very minior for the purpose of demonstration BLE on Android, So, please ignore it.