Thursday, April 20, 2017

Android Sensor - Tutorial


Android Sensors 

              Sensors measure a particular kind of physical quantity, such as force acting on device, light falling on a surface, or the temperature in a room.

               The majority of the sensors are Micro Electro Mechanical Sensors (MEMS), which are made on a tiny scale (in micrometers), usually on a silicon chip, with mechanical and electrical elements integrated together. The basic working principle behind MEMS is to measure the change in electric signal originating due to mechanical motion. This change in electric signals is converted to digital values by electric circuits. The accelerometer and gyroscope are the main examples of MEMS


Types of sensor values

Sensor values can be broadly divided into the following three categories:
  • Raw: These values are directly given by the sensor. The operating system simply passes these values to the apps without adding any correction logic. Accelerometers, proximity sensors, light sensors, and barometers are sensors that give raw values.

  • Calibrated: These values are computed by the operating system by adding extra correction algorithms, such as drift compensation and removing bias and noise over the raw values given by sensors. Step detector, step counter, and significant motion are sensors that give calibrated values by using an accelerometer as their base sensor. The magnetometer and gyroscope are special kinds of sensor that give both raw and calibrated values.

  • Fused: These values are derived from a combination of two or more sensors. Generally, these values are calculated by leveraging the strength of one sensor to accommodate the weaknesses of other sensors. Gravity and linear acceleration give fused values by using the accelerometer and gyroscope.

Types of sensor

Sensor
Value
Underlying Sensors
Description
Common Usage
Accelerometer
Raw
Accelerometer
This measures the acceleration force along the xy, and z axes (including gravity). Unit: m/s2
It can be used to detect motion such as shakes, swings, tilt, and physical forces applied on the phone.
Gravity
Fused
Accelerometer, Gyroscope
This measures the force of gravity along the xy, and zaxes. Unit: m/s2
It can be used to detect when the phone is in free fall.
Linear Acceleration
Fused
Accelerometer, Gyroscope
It measures the acceleration force along the xy, and z axes (excluding gravity). Unit: m/s2
It can be used to detect motion such as shakes, swings, tilt, and physical forces applied on phone.
Gyroscope
Raw, Calibrated
Gyroscope
This measures the rate of rotation of the device along the xy, and zaxes. Unit: rad/s
It can be used to detect rotation motions such as spin, turn, and any angular movement of the phone.
Step Detector
Calibrated
Accelerometer
This detects walking steps.
It can be used to detect when a user starts walking.
Step Counter
Calibrated
Accelerometer
It measures the number of steps taken by the user since the last reboot while the sensor was activated
It keeps track of the steps taken by the user per day.
Significant Motion
Calibrated
Accelerometer
It detects when there is significant motion on the phone because of walking, running, or driving.
It detects a significant motion event.
Rotation Vector
Fused
Accelerometer, Gyroscope, Magnetometer
This measures the rotation vector component along the xaxis (x * sin(θ/2)), y axis (y * sin(θ/2)), and z axis (z * sin(θ/2)). Scalar component of the rotation vector ((cos(θ/2)). Unitless.
It can be used in 3D games based on phone direction.

Android Sensor Stack




Components of the sensor framework

                   Android has provided methods, classes, and interfaces for accessing sensors and their data that is available on an Android device. These sets of methods, classes, and interfaces are collectively referred to as the sensor framework and are a part of the android.hardwarepackage. It consists of four major components: SensorManagerSensorSensorEvent, and SensorEventListener. The entry point to the framework is the SensorManagerclass, which allows an app to request sensor information and register to receive sensor data. When registered, sensor data values are sent to a SensorEventListener interface in the form of a SensorEvent class that contains information produced from a given sensor.


SensorManager

         SensorManager is the class that makes it possible for your app to get access to the sensors. It creates the instance of the system sensor service, which provides various APIs to access sensor information on the device. It exposes the methods that list the available and default sensors on the device. This class also provides several sensor constants that are used to report sensor accuracy, sampling period, and calibrate sensors. One of the important tasks of this class is to register and unregister sensor event listeners for accessing a particular sensor.

SensorEventListener
        SensorEventListener is the interface that provides two callbacks to receive the sensor notification (sensor event). OnSensorChanged() is the first method of the interface, which is called whenever there is any change in the sensor values. The change in sensor value is communicated through the SensorEvent object, passed as a parameter to this method. OnAccuracyChanged() is the second method, which is called whenever there is a change in the accuracy of sensor values. The sensor object and newly reported accuracy in integers are sent as parameters to this method. There are four accuracy integer constants supported by SensorManager. They are as follows:
  • SENSOR_STATUS_ACCURACY_HIGH
  • SENSOR_STATUS_ACCURACY_MEDIUM
  • SENSOR_STATUS_ACCURACY_LOW
  • SENSOR_STATUS_ACCURACY_UNRELIABLE

Sensor
        Sensor is the class that is used to create an instance of a specific sensor. This class provides various methods that let you determine a sensor's capabilities:
  • Maximum Range
  • Minimum Delay
  • Name
  • Power
  • Resolution
  • Reporting Mode
  • Type
  • Vendor
  • Version
  • isWakeUp Sensor

SensorEvent
        SensorEvent is a special kind of class that is used by the operating system to report changes in the sensor values to the listeners. This SensorEvent object contains the following four elements:
  • values[]: This is a multidimensional array that holds the sensor values
  • timestamp: This refers to the time in nanoseconds at which the event happened
  • accuracy: This is one of the four accuracy integer constants
  • sensor: This is the sensor type that generated this data


Checking the availability of the sensor at runtime

private SensorManager mSensorManager;
...
mSensorManager=
(SensorManager)getSystemService(Context.SENSOR_SERVICE);
if(mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)!=null){
  // Success! There's a pressure sensor.
}else{
  // Failure! No pressure sensor.
}

Example
       import android.app.Activity;
       import android.content.Context;
       import android.hardware.Sensor;
       import android.hardware.SensorEvent;
       import android.hardware.SensorEventListener;
       import android.hardware.SensorManager;
       import android.os.Bundle;

       public class SensorActivity extends Activity implements
       SensorEventListener{
       private SensorManager mSensorManager;
       private Sensor mSensor;

       @Override
       protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
    
       mSensorManager =
       (SensorManager)this.getSystemService
       (Context.SENSOR_SERVICE );

       if(mSensorManager.getDefaultSensor
       (Sensor.TYPE_GYROSCOPE)!= null){
       mSensor =
       mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
       }
     }
@Override
       protected void onResume() {
         super.onResume();
         mSensorManager.registerListener(this, mSensor,
         SensorManager.SENSOR_DELAY_NORMAL);
       }

       @Override
       protected void onPause() {
         super.onPause();
         mSensorManager.unregisterListener(this);
       }

       @Override
       protected void onDestroy() {
         super.onDestroy();
         mSensorManager = null;
         mSensor = null;
       }
@Override
        public void onSensorChanged(SensorEvent event) {
            //event.values[] (do something with sensor values)
           //event.timestamp (do something with timestamp)
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int
        accuracy)
        {
            //Do something with changed accuracy
           //This method is mandatory to defined
        }

Listing the available sensors on a device


public class SensorListActivity extends Activity
       implements OnItemClickListener{

         private SensorManager mSensorManager;
         private ListView mSensorListView;
         private ListAdapter mListAdapter;
         private List<Sensor> mSensorsList;
@Override
     protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mSensorManager =
       (SensorManager)this.getSystemService
       (Context.SENSOR_SERVICE);

       mSensorsList =
       mSensorManager.getSensorList(Sensor.TYPE_ALL);
       mSensorListView =
       (ListView)findViewById(R.id.session_list);
       mListAdapter = new ListAdapter();
       mSensorListView.setAdapter(mListAdapter);
       mSensorListView.setOnItemClickListener(this);  
     }
     private class ListAdapter extends BaseAdapter{ 

      private TextView mSensorName;

   
      @Override
      public int getCount() {
        return mSensorsList.size();
      }

      @Override
      public Object getItem(int position) {
        return mSensorsList.get(position).getName();
      }

      @Override
      public long getItemId(int position) {
        return position;
      }

      @Override
      public View getView(int position, View convertView,
      ViewGroup parent) {

      if(convertView==null){
        convertView = 
        getLayoutInflater().inflate(R.layout.list_rows,
        parent, false);
      }

      mSensorName =
      (TextView)convertView.findViewById(R.id.sensor_name);
      mSensorName.setText(mSensorsList.get(position)
      .getName());
      return convertView;
      }
   }
@Override
     public void onItemClick(AdapterView<?> parent, View view,
     int position,long id) {
    
       Intent intent = new Intent(getApplicationContext(),
       SensorCapabilityActivity.class);
       intent.putExtra(getResources()
       .getResourceName(R.string.sensor_type),
       mSensorsList.get(position).getType());
  
       startActivity(intent);
     }

Wake locks, wakeup sensors, and the FIFO queue

Wake lock can be obtained using the PowerManager object, which is provided by the system power service. The newWakeLock() method of PowerManager provides the object of wake lock. This newWakeLock() method accepts the type of wake lock and string tag for identification purposes.
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"myLock");
mWakeLock.acquire();
 //Do some important work in background.
 mWakeLock.release();

Using the fingerprint sensor

          In order to support the fingerprint sensor, the Android platform has introduced a new system service, which is called the Finger Print Service, and it can be accessed using the instance of FingerprintManager.
Fingerprint sensor APIs require install time permission in the AndroidManifest.xmlfile (android.permission.USE_FINGERPRINT) and also runtime permission before using them.

public class FingerPrintActivity extends Activity { 

          private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
          private FingerprintManager mFingerprintManager;
          //Alias for our key in the Android Key Store
          private static final String KEY_NAME = "my_key";
          private KeyStore mKeyStore;
          private KeyGenerator mKeyGenerator;
          private Cipher mCipher;
          private CancellationSignal mCancellationSignal;
          private Dialog mFingerPrintDialog;

          @Override
          protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.fingerprint_layout);
            mFingerprintManager = (FingerprintManager)getSystemService
            (FINGERPRINT_SERVICE);
            //As soon as Activity starts, check for the finger print
            conditions
            checkFingerPrintConditions()
          }

          public void initiateFingerPrintSensor(View v) {
            //Called from Layout button
            checkFingerPrintConditions();
          }


public void checkFingerPrintConditions() {

          if(mFingerprintManager.isHardwareDetected()) {
            if(mFingerprintManager.hasEnrolledFingerprints()) {
              if(ContextCompat.checkSelfPermission(this,
              Manifest.permission.USE_FINGERPRINT)!=
              PackageManager.PERMISSION_GRANTED) {
                //Requesting runtime finger print permission
                requestPermissions(new String[]
                {Manifest.permission.USE_FINGERPRINT},
                FINGERPRINT_PERMISSION_REQUEST_CODE);
              } else {
                //After all 3 conditions are met, then show FingerPrint
                Dialog
                showFingerPrintDialog();
              }
            } else {
              showAlertDialog("Finger Print Not Registered!", "Go to
              'Settings -> Security -> Fingerprint' and register at least
              one fingerprint");
            }
          } else {
            showAlertDialog("Finger Print Sensor Not Found!", "Finger Print
            Sensor could not be found on your phone.");
          }
        }

        @Override
        public void onRequestPermissionsResult(int requestCode, String[]
        permissions, int[] state) {

          //show FingerPrint Dialog, when runtime permission is granted
          if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE
          && state[0] == PackageManager.PERMISSION_GRANTED) {

            showFingerPrintDialog();
          }
        }

        public void showAlertDialog(String title, String message){
          new android.app.AlertDialog.Builder(this).setTitle(title)    
          .setMessage(message).setIcon(android.R.drawable.ic_dialog_alert)
          .setPositiveButton("Cancel", new DialogInterface
          .OnClickListener()               
          {
             public void onClick(DialogInterface dialog, int whichButton) 
             {
                dialog.dismiss();
             }})
            .show();
          }


public void showFingerPrintDialog() {
          //First Initialize the FingerPrint Settings
          if(initFingerPrintSettings())
          {
            //Init Custom FingerPrint Dialog from xml
            mFingerPrintDialog = new Dialog(this);
            View view = LayoutInflater.from(this).inflate
            (R.layout.fingerpring_dialog, null, false);
            mFingerPrintDialog.setContentView(view);
            Button cancel = (Button) view.findViewById(R.id.cancelbutton);
            cancel.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View arg0) {
                mCancellationSignal.cancel();
                mFingerPrintDialog.dismiss();
              }
            });

            //Stops the cancelling of the fingerprint dialog
            //by back press or touching accidentally on screen
            mFingerPrintDialog.setCanceledOnTouchOutside(false);
            mFingerPrintDialog.setCancelable(false);
            mFingerPrintDialog.show();
          }
          else
          {
            showAlertDialog("Error!", "Error in initiating Finger Print
            Cipher or Key!");
          }
        }

        public boolean initFingerPrintSettings() {

          //CancellationSignal requests authenticate api to stop scanning 
          mCancellationSignal = new CancellationSignal();
          if(initKey() && initCipher()) {
            mFingerprintManager.authenticate(new
            FingerprintManager.CryptoObject(mCipher),
            mCancellationSignal, 0, new AuthenticationListener(), null);
            return true;
          } else {
            return false;
          }
        }

public boolean initKey() {
          try {
            mKeyStore = KeyStore.getInstance("AndroidKeyStore");
            mKeyStore.load(null);
            mKeyGenerator = KeyGenerator.getInstance
            (KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setUserAuthenticationRequired(true)                
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .build());
            mKeyGenerator.generateKey();
            return true;
          } catch (Exception e) {
            return false;
          }
        }

        public boolean initCipher() {
          try {
            mKeyStore.load(null);
            SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); 
            mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES +
            "/" + KeyProperties.BLOCK_MODE_CBC + "/" +
            KeyProperties.ENCRYPTION_PADDING_PKCS7);
            mCipher.init(Cipher.ENCRYPT_MODE, key);
            return true;
          } catch (KeyStoreException | CertificateException |
          UnrecoverableKeyException | IOException |
          NoSuchAlgorithmException | InvalidKeyException |
          NoSuchPaddingException e) {
            return false;
          }
        }


  class AuthenticationListener extends
        FingerprintManager.AuthenticationCallback{

          @Override
          public void onAuthenticationError(int errMsgId, CharSequence
          errString) {

            Toast.makeText(getApplicationContext(), "Authentication
            Error!", Toast.LENGTH_LONG).show();
          }

          @Override
          public void onAuthenticationHelp(int helpMsgId, CharSequence
          helpString) {
          }

          @Override
          public void onAuthenticationFailed() {

            Toast.makeText(getApplicationContext(), "Authentication
            Failed!", Toast.LENGTH_LONG).show();
          }

          @Override
          public void onAuthenticationSucceeded
          (FingerprintManager.AuthenticationResult result) {
             Toast.makeText(getApplicationContext(), "Authentication
            Success!", Toast.LENGTH_LONG).show();
            mFingerPrintDialog.dismiss();
          }
        }

The Step Counter and Detector Sensors – The Pedometer App

    public class StepsCounterActivity extends Activity
        implements SensorEventListener{

        private SensorManager mSensorManager;
        private Sensor mSensor;
        private boolean isSensorPresent;
        private TextView mStepsSinceReboot;

        @Override
        protected void onCreate(Bundle savedInstanceState){
          super.onCreate(savedInstanceState);
          setContentView(R.layout.stepcounter_layout);
        
          mStepsSinceReboot = (TextView)findViewById
          (R.id.stepssincereboot);
        
          mSensorManager = (SensorManager)
          this.getSystemService(Context.SENSOR_SERVICE);
          if(mSensorManager.getDefaultSensor
          (Sensor.TYPE_STEP_COUNTER) != null) {
            mSensor = mSensorManager.getDefaultSensor
            (Sensor.TYPE_STEP_COUNTER);
            isSensorPresent = true;
          } else {
            isSensorPresent = false;
          }
        }

        @Override
        protected void onResume() {
          super.onResume();
          if(isSensorPresent) {
            mSensorManager.registerListener(this, mSensor,
            SensorManager.SENSOR_DELAY_NORMAL);
          }
        }

        @Override
        protected void onPause() {
          super.onPause();
          if(isSensorPresent) {
            mSensorManager.unregisterListener(this);
          }
        }

        @Override
        public void onSensorChanged(SensorEvent event) {
          mStepsSinceReboot.setText("Steps since reboot:" +
          String.valueOf(event.values[0]));
        }
Share:

Sunday, April 2, 2017

Android NDK Tutorial


Android Native Development Kit (NDK)


                      Android apps are typically written in Java, with its elegant object-oriented design. However, at times, you need to overcome the limitations of Java, such as memory management and performance, by programming directly into Android native interface. Android provides Native Development Kit (NDK) to support native development in C/C++, besides the Android Software Development Kit (Android SDK) which supports Java.

Passing value from java to C

In Java


private native short passIntReturnInt (int p);

static {
System.loadLibrary("PassingPrimitive");
}
In C

#include <jni.h>
#include <android/log.h>
JNIEXPORT jint JNICALL Java_cookbook_chapter2_PassingPrimitiveActivity_passIntReturnInt(JNIEnv *pEnv, jobject pObj, jint pIntP) {
__android_log_print(ANDROID_LOG_INFO, "native", "%d in %d bytes", pIntP, sizeof(jint));
return pIntP + 1;
}

In Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := PassingPrimitive
LOCAL_SRC_FILES := primitive.c
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

Manipulating strings in JNI

                        Strings are somewhat complicated in JNI, mainly because Java strings and C strings are internally different. Understanding the basics of encoding is essential to comprehend the differences between Java string and C string. The Unicode Standard is a character coding system designed to support the worldwide interchange, processing, and display of the written texts of the diverse languages and technical disciplines of the modern world. Unicode assigns a unique number for each character it defines, called code point. There are mainly two categories of encoding methods that support the entire Unicode character set, or a subset of it.

                   The first one is the Unicode Transformation Format (UTF), which encodes a Unicode code point into a variable number of code values. UTF-8, UTF-16, UTF-32, and a few others belong to this category. The numbers 8, 16, and 32 refer to the number of bits in one code value. The second category is the Universal Character Set (UCS) encodings, which encodes a Unicode code point into a single code value. UCS2 and UCS4 belong to this category. The numbers 2 and 4 refer to the number of bytes in one code value. 


JNIEXPORT jstring JNICALL Java_cookbook_chapter2_StringManipulationActivity_passStringReturnString(JNIEnv *pEnv, jobject pObj, jstring pStringP){

__android_log_print(ANDROID_LOG_INFO, "native", "print jstring: %s", pStringP);
const jbyte *str;
jboolean *isCopy;
str = (*pEnv)->GetStringUTFChars(pEnv, pStringP, isCopy);
__android_log_print(ANDROID_LOG_INFO, "native", "print UTF-8 string: %s, %d", str, isCopy);

jsize length = (*pEnv)->GetStringUTFLength(pEnv, pStringP);
__android_log_print(ANDROID_LOG_INFO, "native", "UTF-8 string length (number of bytes): %d == %d", length, strlen(str));
__android_log_print(ANDROID_LOG_INFO, "native", "UTF-8 string ends with: %d %d", str[length], str[length+1]);
(*pEnv)->ReleaseStringUTFChars(pEnv, pStringP, str);
char nativeStr[100];
(*pEnv)->GetStringUTFRegion(pEnv, pStringP, 0, length, nativeStr);
__android_log_print(ANDROID_LOG_INFO, "native", "jstring converted to UTF-8 string and copied to native buffer: %s", nativeStr);

const char* newStr = "hello 安卓";
jstring ret = (*pEnv)->NewStringUTF(pEnv, newStr);
jsize newStrLen = (*pEnv)->GetStringUTFLength(pEnv, ret);
__android_log_print(ANDROID_LOG_INFO, "native", "UTF-8 string with Chinese characters: %s, string length (number of bytes) %d=%d", newStr, newStrLen, strlen(newStr));
return ret;

}

Ex : JNIEXPORT void JNICALL Java_cookbook_chapter2_ManagingReferenceActivity_weakReference(JNIEnv *pEnv, jobject pObj, jstring pStringP, jboolean pDelete){
static jstring stStr;
const jbyte *str;
jboolean *isCopy;
if (NULL == stStr) {
stStr = (*pEnv)->NewWeakGlobalRef(pEnv, pStringP);
}
str = (*pEnv)->GetStringUTFChars(pEnv, stStr, isCopy);
if (pDelete) {
(*pEnv)->DeleteWeakGlobalRef(pEnv, stStr);
stStr = NULL;
}
}

Manipulating classes in JNI

Class descriptor: A class descriptor refers to the name of a class or an interface. It can be derived by replacing the "." character in Java with "/" in JNI programming. For example, the descriptor for class java.lang.String is java/lang/String.


FindClass and class loader: The JNI function FindClass has the following prototype:

jclass FindClass(JNIEnv *env, const char *name);

GetSuperclass: The JNI function GetSuperclass has the following prototype:

jclass GetSuperclass(JNIEnv *env, jclass clazz);

Manipulating objects in JNI

Create instance objects in the native code: Four JNI functions can be used to create instance objects of a Java class in the native code, namely AllocObject, NewObject,NewObjectA, and NewObjectV.

The AllocObject function creates an uninitialized object, while the other three methods take a constructor as an input parameter to create the object. The prototypes for the four functions are as follows:


jobject AllocObject(JNIEnv *env, jclass clazz);

jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...);

jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, jvalue *args);

jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args);

GetObjectClass: This JNI function has the following prototype:

jclass GetObjectClass(JNIEnv *env, jobject obj);


It returns a local reference to the class of the instance object obj. The obj argument must not be NULL, otherwise it will cause the VM to crash.


IsInstanceOf: This JNI function call has the following prototype:
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);


Manipulating arrays in JNI

Arrays are represented by jarray or its subtypes such as jobjectArray andjbooleanArray.

Create new arrays: JNI provides NewObjectArray and New<Type>Array functions to create arrays for objects and primitive types. Their function prototypes are as follows:


jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementType, jobject initialElement);

<ArrayType> New<Type>Array(JNIEnv *env, jsize length);

GetArrayLength: This native function has the following prototype:

jsize GetArrayLength(JNIEnv *env, jarray array);

Access object arrays: JNI provides two functions to access object arrays, namelyGetObjectArrayElement and SetObjectArrayElement.

The two functions have the following prototype:

jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

Access arrays of primitive types: void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf);
void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf);

Secondly, if we want to access a large array, then GetIntArrayElements andReleaseIntArrayElements are the JNI functions for us. They have the following prototype: jint *GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy);
void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode);

Accessing Java static and instance fields in the native code the access of fields (both static and instance fields) in Java from native code:

jfieldID data type: jfieldID is a regular C pointer pointing to a data structure with details hidden from developers.

Accessing static fields: JNI provides three functions to access static fields of a Java class. They have the following prototypes:

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
<NativeType> GetStatic<Type>Field(JNIEnv *env,jclass clazz, jfieldID fieldID);

void SetStatic<Type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID,<NativeType> value);

Accessing instance field: Accessing instance fields is similar to accessing static fields. JNI also provides the following three functions for us:


jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

<NativeType> Get<Type>Field(JNIEnv *env,jobject obj, jfieldID fieldID);

void Set<Type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, <NativeType> value);

Calling static and instance methods from the native code

jmethodID data type: Similar to jfieldID, jmethodID is a regular C pointer pointing to a data structure with details hidden from the developers.
Method descriptor: This is a modified UTF-8 string used to represent the input (input arguments) data types and output (return type) data type of the method.



Calling static methods: JNI provides four sets of functions for native code to call Java methods. Their prototypes are as follows:

jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

<NativeType> CallStatic<Type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

<NativeType> CallStatic<Type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

<NativeType> CallStatic<Type>MethodV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args);

Calling instance methods: Calling instance methods from the native code is similar to calling static methods. JNI also provides four sets of functions as follows:

jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

<NativeType> Call<Type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);

<NativeType> Call<Type>MethodA(JNIEnv *env,jobject obj, jmethodID methodID, jvalue *args);

<NativeType> Call<Type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
Caching jfieldID, jmethodID, and referencing data to improve performance


The first approach caches at the class initializer. In Java, we can have something similar to the following: private native static void InitIDs();
static {
System.loadLibrary(<native lib>);
InitIDs();
}

The second approach caches the IDs at the point of usage. We store the field or method ID in a static variable, so that the ID is valid the next time the native method is invoked.
Checking errors and handling exceptions in JNI

JNI defines two functions to check for exceptions, as follows: jboolean ExceptionCheck(JNIEnv *env);
jthrowable ExceptionOccurred(JNIEnv *env);

When the second function is used, an additional JNI function can be called to examine the details of the exception: void ExceptionDescribe(JNIEnv *env);

There are generally two ways to handle an exception. The first approach is to free the resources allocated at JNI and return. This will leave the responsibility of handling the exception to the caller of the native method.

The second practice is to clear the exception and continue executing. This is done through the following JNI function call: void ExceptionClear(JNIEnv *env);

Throw exceptions in the native code: JNI provides two functions to throw an exception from native code. They have the following prototypes:

jint Throw(JNIEnv *env, jthrowable obj);

jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);

Fatal error: A special type of error is the fatal error, which is not recoverable. JNI defines a function FatalError, as follows, to raise a fatal error:

void FatalError(JNIEnv *env, const char *msg);


Integrating assembly code in JNI

Android NDK allows you to write assembly code at JNI programming. Assembly code is sometimes used to optimize the critical portion of code to achieve the best performance.

Compile assembly $ $ANDROID_NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin/arm-linux-androideabi-gcc -S tmp.c -o AssemblyMultiplyDemo.s --sysroot=$ANDROID_NDK/platforms/android-14/arch-arm/

The usage of the assembly code to implement a native method:

Inline assembly at C code: We can write inline assembly code for Android NDK development.

Generating a separate assembly code: One approach to write assembly code is to write the code in C or C++, and use a compiler to compile the code into assembly code. $ $ANDROID_NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin/arm-linux-androideabi-gcc -S <c_file_name>.c -o <output_file_name>.s --sysroot=$ANDROID_NDK/platforms/android-<level>/arch-<arch>/

Compile the assembly code: Compiling assembly code is just like compiling C/C++ source code. As shown in the Android.mk file, we simply list the assembly file as a source file as follows:

LOCAL_SRC_FILES := AssemblyMultiplyDemo.s assemblyinjni.c


Building Android NDK applications for different CPU features

Android NDK contains a library named cpufeatures, which can be used to detect the CPU family and optional features at runtime.

Add it in the static library list in Android.mk as follows:

LOCAL_STATIC_LIBRARIES := cpufeatures

At the end of the Android.mk file, import the cpufeatures module:

$(call import-module,cpufeatures)

In the code, include the header file <cpu-features.h>

Get the CPU family. The function prototype is as follows:

AndroidCpuFamily android_getCpuFamily();

It returns an enum. The supported CPU families are listed in the section to follow. ANDROID_CPU_FAMILY_MIPS
ANDROID_CPU_FAMILY_MIPS
ANDROID_CPU_FAMILY_ARM

For the ARM CPU family, the supported CPU feature detections are as follows:
ANDROID_CPU_ARM_FEATURE_ARMv7: It means that the ARMv7-a instruction is supported.
ANDROID_CPU_ARM_FEATURE_VFPv3: It means that the VFPv3 hardware FPU instruction set extension is supported. Note that this refers to VFPv3-D16, which provides 16 hardware FP registers.
ANDROID_CPU_ARM_FEATURE_NEON: It means that he ARM Advanced SIMD (also known as NEON) vector instruction set extension is supported. Note that such CPUs also support VFPv3-D32, which provides 32 hardware FP registers.

Debugging an Android NDK application with logging messages

Android logging system provides a method for collecting logs from various applications into a series of circular buffers. The logcat command is used to view the logs.


mylog.h #include <android/log.h>
#define LOG_LEVEL 9
#define LOG_TAG "NDKLoggingDemo"

#define LOGU(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_UNKNOWN, LOG_TAG, __VA_ARGS__);}
#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEFAULT, LOG_TAG, __VA_ARGS__);}
#define LOGV(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);}
#define LOGDE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
#define LOGF(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__);}
#define LOGS(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_SILENT, LOG_TAG, __VA_ARGS__);}

#endif

void outputLogs() {
LOGU(9, "unknown log message");
LOGD(8, "default log message");
LOGV(7, "verbose log message");
LOGDE(6, "debug log message");
LOGI(5, "information log message");
LOGW(4, "warning log message");
LOGE(3, "error log message");
LOGF(2, "fatal error log message");
LOGS(1, "silent log message");
}

Debugging an Android NDK application with NDK GDB Android NDK introduces a shell script named ndk-gdb to help one to launch a debugging session to debug the native code.

The application is built with the ndk-build command.
AndroidManifest.xml has the android:debuggable attribute of the<application> element set to true. This indicates that the application is debuggable even when it is running on a device in the user mode. Make sure that the debuggable attribute in AndroidManifest.xml is set to true.

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:debuggable="true"
>

Build the native library with the command "ndk-build NDK_DEBUG=1".


Run the application on an Android device. Then, start a terminal and enter the following command: $ ndk-gdb


Android NDK Multithreading


At Android NDK, POSIX Threads(pthreads) is bundled in Android's Bionic C library to support multithreading. This chapter mainly discusses the API functions defined in the pthread.h and semaphore.h header files,

Thread creation
Syntax : int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
void pthread_exit(void *value_ptr);
int pthread_join(pthread_t thread, void **value_ptr);

Ex: void jni_start_threads() {
pthread_t th1, th2;
int threadNum1 = 1, threadNum2 = 2;
int ret;
ret = pthread_create(&th1, NULL, run_by_thread, (void*)&threadNum1); // run_by_thread is function name
ret = pthread_create(&th2, NULL, run_by_thread, (void*)&threadNum2);
void *status;
ret = pthread_join(th1, &status);
int* st = (int*)status;
LOGI(1, "thread 1 end %d %d", ret, *st);
ret = pthread_join(th2, &status);
st = (int*)status;
LOGI(1, "thread 2 end %d %d", ret, *st);
}

Why we need Synchronizing
As per operating system terminology, mutex and semaphore are kernel resources that provide synchronization services (also called as synchronization primitives).

The producer-consumer problem:

Note that the content is generalized explanation. Practical details vary with implementation.

Consider the standard producer-consumer problem. Assume, we have a buffer of 4096 byte length. A producer thread collects the data and writes it to the buffer. A both the threads should not run at the same time.consumer thread processes the collected data from the buffer. Objective is,Using Mutex:

A mutex provides mutual exclusion, either producer or consumer can have the key (mutex) and proceed with their work. As long as the buffer is filled by producer, the consumer needs to wait, and vice versa.

At any point of time, only one thread can work with the entire buffer. The concept can be generalized using semaphore.

Using Semaphore:

A semaphore is a generalized mutex. In lieu of single buffer, we can split the 4 KB buffer into four 1 KB buffers (identical resources). A semaphore can be associated buffers at the same time.with these four buffers. The consumer and producer can work on different Strictly speaking, a mutex is locking mechanism used to synchronize access to a resource. Only one task (can be a thread or process based on OS abstraction) can acquire the mutex. It means there is ownership associated with mutex, and only the owner can release the lock (mutex).


Semaphore is signaling mechanism (“I am done, you can carry on” kind of signal). For example, if you are listening songs (assume it as one task) on your mobile and at the same time your friend calls you, an interrupt is triggered upon which an interrupt service routine (ISR) signals the call processing task to wakeup.

Mutex:

Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.

Officially: "Mutexes are typically used to serialise access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section." Ref: Symbian Developer Library

(A mutex is really a semaphore with value 1.)

Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.


Officially: "A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore)." Ref: Symbian Developer Library

Synchronizing native threads with mutex at Android NDK

A mutex can be initialized with the pthread_mutex_init function, which has the following prototype: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

USING THE MUTEX

The following four functions are available to lock and unlock a mutex: int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_lock_timeout_np(pthread_mutex_t *mutex, unsigned msecs);

int cnt = 0;
int THR = 10;
void *run_by_thread1(void *arg) {
int* threadNum = (int*)arg;
while (cnt < THR) {
pthread_mutex_lock(&mux1);
while ( pthread_mutex_trylock(&mux2) ) {
pthread_mutex_unlock(&mux1); //avoid deadlock
usleep(50000); //if failed to get mux2, release mux1 first
pthread_mutex_lock(&mux1);
}
++cnt;
LOGI(1, "thread %d: cnt = %d", *threadNum, cnt);
pthread_mutex_unlock(&mux1);
pthread_mutex_unlock(&mux2);
sleep(1);
}
}

Synchronizing native threads with conditional variables at Android NDK pthread_mutex_t mux;
pthread_cond_t cond;
void jni_start_threads() {
pthread_t th1, th2;
int threadNum1 = 1, threadNum2 = 2;
int ret;
pthread_mutex_init(&mux, NULL);
pthread_cond_init(&cond, NULL);
ret = pthread_create(&th1, NULL, run_by_thread1,
void*)&threadNum1);
LOGI(1, "thread 1 started");
ret = pthread_create(&th2, NULL, run_by_thread2,
void*)&threadNum2);
LOGI(1, "thread 2 started");
ret = pthread_join(th1, NULL);
LOGI(1, "thread 1 end %d", ret);
ret = pthread_join(th2, NULL);
LOGI(1, "thread 2 end %d", ret);
pthread_mutex_destroy(&mux);
pthread_cond_destroy(&cond);
}


int cnt = 0;
int THR = 10, THR2 = 5;
void *run_by_thread1(void *arg) {
int* threadNum = (int*)arg;
pthread_mutex_lock(&mux);
while (cnt != THR2) {
LOGI(1, "thread %d: about to wait", *threadNum);
pthread_cond_wait(&cond, &mux);
}
++cnt;
LOGI(1, "thread %d: cnt = %d", *threadNum, cnt);
pthread_mutex_unlock(&mux);
}

Programming with the dynamic linker library in Android NDK


Dynamic loading is a technique to load a library into memory at runtime, and execute functions or access variables defined in the library. It allows the app to start without these libraries. void naDLDemo(JNIEnv* pEnv, jclass clazz) {
void *handle;
double (*sqrt)(double);
const char *error;
handle = dlopen("libm.so", RTLD_LAZY);
if (!handle) {
LOGI(1, "%s\n", dlerror());
return;
}
dlerror(); /* Clear any existing error */
*(void **) (&sqrt) = dlsym(handle, "sqrt");
if ((error = dlerror()) != NULL) {
LOGI(1, "%s\n", error);
return;
}
LOGI(1, "%f\n", (*sqrt)(2.0));
}

Add an Android.mk file under the jni folder with the following content:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := DynamicLinker
LOCAL_SRC_FILES := DynamicLinker.cpp
LOCAL_LDLIBS := -llog -ldl
include $(BUILD_SHARED_LIBRARY)


The following functions are defined in the dlfcn.h header file by the Android dynamic linking library: void* dlopen(const char* filename, int flag);
int dlclose(void* handle);
const char* dlerror(void);
void* dlsym(void* handle, const char* symbol);
int dladdr(const void* addr, Dl_info *info);

Understand the Android.mk files

Android NDK provides an easy-to-use build system, which frees us from writing makefiles. However, we still need to provide some basic inputs to the system through Android.mk and Application.mk.

The Android.mk file is a GNU makefile fragment that describes the sources to the Android build system. The sources are grouped into modules. Each module is a static or shared library. The Android NDK provides a few predefined variables and macros.

CLEAR_VARS: This variable points to a script, which undefines nearly all module description variables except LOCAL_PATH include $(CLEAR_VARS)

BUILD_SHARED_LIBRARY: This variable points to a build script, which determines how to build a shared library from the sources listed, based on the module description. We must have LOCAL_MODULE and LOCAL_SRC_FILES defined when including this variable, as follows:

include $(BUILD_SHARED_LIBRARY)

my-dir: The my-dir macro returns the path of the last included makefile, which is usually the directory containing the current Android.mk file. It is typically used to define the LOCAL_PATH, as follows:

LOCAL_PATH := $(call my-dir)

all-subdir-makefiles: This macro returns a list of Android.mk files located in all subdirectories of the current my-dir path. include $(call all-subdir-makefiles)

LOCAL_PATH: This is a module description variable, which is used to locate the path to the sources. It is usually used with the my-dir macro, as follows:

LOCAL_PATH := $(call my-dir)

LOCAL_MODULE: This is a module description variable, which defines the name of our module.

LOCAL_SRC_FILES: This is a module description variable, which lists out the sources used to build the module.

LOCAL_C_INCLUDES: This is an optional module description variable, which provides a list of the paths that will be appended to the include search path at compilation. LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libbmp/

LOCAL_SHARED_LIBRARIES: This is an optional module description variable, which provides a list of the shared libraries the current module depends on. LOCAL_SHARED_LIBRARIES := libbmp

LOCAL_LDLIBS: This is an optional module description variable, which provides a list of linker flags. It is useful to pass the system libraries with the -l prefix. LOCAL_LDLIBS := -llog

Using a library as a prebuilt library

The content of this Android.mk file is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libbmp-prebuilt
LOCAL_SRC_FILES := libbmp-0.1.3/lib/libbmp.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libbmp-0.1.3/include/
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := PortingWithBuildSystem
LOCAL_SRC_FILES := PortingWithBuildSystem.c
LOCAL_STATIC_LIBRARIES := libbmp-prebuilt
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

Share:

Popular Posts

Contact Form

Name

Email *

Message *