Friday, September 26, 2014

Port JavaFX application fo Android APK, with javafxports

javafxports provide tools to build a Java(FX) runtime for Android devices. You can follow the instructions to port JavaFX Application to Android APK.

Here show how to port JavaFX Hello World to Android APK, on Ubuntu.

JavaFXAndroid
- It's assumed Android SDK is install on your system, at /home/eric/Android in my case.

- Gradle is need to create Android project from JavaFX application. To install Gradle (currently 1.4) on Ubuntu, run the command:
$ sudo apt-get install gradle


- You need a JavaFX-Dalvik Runtime. You can either download it here (easy) or build this yourself (less trivial). Always download the latest version of the runtime: dalvik-sdk-latest. /home/eric/dalvik-sdk in my case.


- Create a JavaFX Hello World project compiled with Java 7. To specify compiled with Java 7 in Netbeans, click File (or right click the project), -> Project Properties, select Category of Sources, select Source/Binary Format of JDK 7.


- To run Gradle to generate Android project, and ant to generate APK, the following environment setting have to be set.

path to Android SDK:
$ export ANDROID_SDK=/home/eric/Android/sdk
$ export PATH=$ANDROID_SDK/tools:$ANDROID_SDK/platform-tools:$PATH

Android and Java Home:
$ export ANDROID_HOME=/home/eric/Android/sdk
$ export JAVA_HOME=/home/eric/jdk1.8.0_20

- Switch to /home/eric/dalvik-sdk/android-tools, run the command to generate Android Project:

gradle -PDEBUG -PDIR=/home/eric/JavaFXAndroid -PNAME=JavaFXAndroid -PPACKAGE=com.JavaFXAndroid -PJFX_SDK=/home/eric/dalvik-sdk -PJFX_APP=/home/eric/NetBeansProjects/com.JavaFXAndroid/dist -PJFX_MAIN=com.javafxandroid.ComJavaFXAndroid -PANDROID_SDK=/home/eric/Android/sdk createProject

Where:

DIR=/home/eric/JavaFXAndroid - the output directory.
NAME=JavaFXAndroid - Name of the target Android project.
PACKAGE=com.JavaFXAndroid - package name of Java application
JFX_SDK=/home/eric/dalvik-sdk - location of downloaded JavaFX-Dalvik Runtime.
JFX_APP=/home/eric/NetBeansProjects/com.JavaFXAndroid/dist - The location of the Netbeans JavaFX project.
JFX_MAIN=com.javafxandroid.ComJavaFXAndroid - fully name of the main class of your JavaFX Application
ANDROID_SDK=/home/eric/Android/sdk createProject - location of your Android SDK.

Once successfully finished, switch to the the directory of the generated Android Project,  /home/eric/JavaFXAndroid/JavaFXAndroid in my case.

Run the command to generate the APK in its bin directory.
$ ant debug

Then you can run the adb command in bin directory to install the APK to real Android devices.
$ adb install -r -debug.apk


Run the ported Android app on real device:


Wednesday, September 24, 2014

Bi-directional communication between Android and Arduino in USB Host Mode, example 2

It's another example to implement Bi-directional communication between Android and Arduino in USB Host Mode. A button on Andndroid is used to turn ON/OFF the on-board LED of Arduino Uno, and EditText to send string to Arduino Uno. it will be displayed on connected LCD, and sent back to Android.


Notice:
- Sync word of '0xFF' is used to mark the beginning of data, to sync the communication. Extra in-correct '0x00' received in Android side (I don't know why), so have to ignore it. Make sure the data will not be '0xFF' and '0x00'.
- Command 0x01, 0x02, and 0x03 are used to define LED ON, LED OFF, and string of text.
- Data will lost if sent too fast, so I break the message and insert dummy delay between bytes. (inside method sendArduinoText())
- A USB OTG cable is need to connect to Android side, and a normal USB is used to connect USB OTG cable and Arduino Uno.


Android side:

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

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk
        android:minSdkVersion="12"
        android:targetSdkVersion="19" />

    <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>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>

</manifest>

/res/xml/device_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- idVendor=2341, idProduct=0043 for Arduino Uno R3 -->
    <usb-device 
        vendor-id="9025" 
        product-id="0067" />
</resources>

/res/layout/activity_main.xml

<LinearLayout 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:orientation="vertical"
    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.example.androidusbhostarduino.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <ToggleButton 
        android:id="@+id/arduinoled"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textOn="ON"
        android:textOff="OFF" />
    
    <EditText
        android:id="@+id/textout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send"/>
    <TextView
        android:id="@+id/textin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

MainActivity.java
package com.example.androidusbhostarduino;

import java.nio.ByteBuffer;

import android.support.v7.app.ActionBarActivity;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class MainActivity extends ActionBarActivity implements Runnable{
 
 private static final String TAG = "AndroidUsbHostArduino";

 private static final byte IGNORE_00 = (byte) 0x00;
 private static final byte SYNC_WORD = (byte) 0xFF;
 
 private static final int CMD_LED_OFF = 2;
 private static final int CMD_LED_ON = 1;
 private static final int CMD_TEXT = 3;
 private static final int MAX_TEXT_LENGTH = 16;

 ToggleButton buttonLed;
 EditText textOut;
 Button buttonSend;
 TextView textIn;
 
 String stringToRx;
 
 private UsbManager usbManager;
    private UsbDevice deviceFound;
    private UsbDeviceConnection usbDeviceConnection;
    private UsbInterface usbInterfaceFound = null;
 private UsbEndpoint endpointOut = null;
 private UsbEndpoint endpointIn = null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  buttonLed = (ToggleButton)findViewById(R.id.arduinoled);
  buttonLed.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView,
     boolean isChecked) {
    if(isChecked){
     sendArduinoCommand(CMD_LED_ON);
    }else{
     sendArduinoCommand(CMD_LED_OFF);
    }
   }});
  
  textOut = (EditText)findViewById(R.id.textout);
  textIn = (TextView)findViewById(R.id.textin);
  buttonSend = (Button)findViewById(R.id.send);
  buttonSend.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    final String textToSend = textOut.getText().toString();
    if(textToSend!=""){
     stringToRx = "";
     textIn.setText("");
     Thread threadsendArduinoText = 
      new Thread(new Runnable(){

       @Override
       public void run() {
        sendArduinoText(textToSend);
       }});
     threadsendArduinoText.start();
    }
    
   }});
  
  usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
 }

 @Override
    public void onResume() {
        super.onResume();

        Intent intent = getIntent();
        String action = intent.getAction();

        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
        if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
            setDevice(device);
        } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            if (deviceFound != null && deviceFound.equals(device)) {
                setDevice(null);
            }
        }
    }
 
 private void setDevice(UsbDevice device) {
        usbInterfaceFound = null;
     endpointOut = null;
     endpointIn = null;

        for (int i = 0; i < device.getInterfaceCount(); i++) {         
   UsbInterface usbif = device.getInterface(i);

   UsbEndpoint tOut = null;
   UsbEndpoint tIn = null;

   int tEndpointCnt = usbif.getEndpointCount();
   if (tEndpointCnt >= 2) {
    for (int j = 0; j < tEndpointCnt; j++) {
     if (usbif.getEndpoint(j).getType() 
      == UsbConstants.USB_ENDPOINT_XFER_BULK) {
      if (usbif.getEndpoint(j).getDirection() 
       == UsbConstants.USB_DIR_OUT) {
       tOut = usbif.getEndpoint(j);
      } else if (usbif.getEndpoint(j).getDirection() 
       == UsbConstants.USB_DIR_IN) {
       tIn = usbif.getEndpoint(j);
      }
     }
    }

    if (tOut != null && tIn != null) {
     // This interface have both USB_DIR_OUT
     // and USB_DIR_IN of USB_ENDPOINT_XFER_BULK
     usbInterfaceFound = usbif;
     endpointOut = tOut;
     endpointIn = tIn;
    }
   }
  }
        
        if (usbInterfaceFound == null) {
            return;
        }

        deviceFound = device;
        
        if (device != null) {
            UsbDeviceConnection connection = 
             usbManager.openDevice(device);
            if (connection != null && 
             connection.claimInterface(usbInterfaceFound, true)) {
             
             connection.controlTransfer(0x21, 34, 0, 0, null, 0, 0);
             connection.controlTransfer(0x21, 32, 0, 0, 
               new byte[] { (byte) 0x80, 0x25, 0x00, 
                0x00, 0x00, 0x00, 0x08 }, 
               7, 0);
             
                usbDeviceConnection = connection;
                Thread thread = new Thread(this);
                thread.start();

            } else {
                usbDeviceConnection = null;
            }
         }
    }
 
 private void sendArduinoCommand(int control) {
        synchronized (this) {

            if (usbDeviceConnection != null) {
                byte[] message = new byte[2];
                message[0] = SYNC_WORD;
                message[1] = (byte)control;

                usbDeviceConnection.bulkTransfer(endpointOut,
                  message, message.length, 0);
                
                Log.d(TAG, "sendArduinoCommand: " + String.valueOf(control));
            }
        }
    }
 
 private void sendArduinoText(String s) {
        synchronized (this) {

            if (usbDeviceConnection != null) {
             
             Log.d(TAG, "sendArduinoText: " + s);
             
             int length = s.length();
             if(length>MAX_TEXT_LENGTH){
              length = MAX_TEXT_LENGTH;
             }
                byte[] message = new byte[length + 3];
                message[0] = SYNC_WORD;
                message[1] = (byte)CMD_TEXT;
                message[2] = (byte)length;
                s.getBytes(0, length, message, 3);
                
                /*
                usbDeviceConnection.bulkTransfer(endpointOut,
                  message, message.length, 0);
                */
                
                byte[] b = new byte[1];
                for(int i=0; i< length+3; i++){
                 b[0] = message[i];
                 Log.d(TAG, "sendArduinoTextb[0]: " + b[0]);
                 usbDeviceConnection.bulkTransfer(endpointOut,
                      b, 1, 0);
                 try {
      Thread.sleep(100);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
                }
            }
        }
    }

 @Override
 public void run() {
  ByteBuffer buffer = ByteBuffer.allocate(1);
        UsbRequest request = new UsbRequest();
        request.initialize(usbDeviceConnection, endpointIn);
        while (true) {
            request.queue(buffer, 1);
            if (usbDeviceConnection.requestWait() == request) {
                byte dataRx = buffer.get(0);
                Log.d(TAG, "dataRx: " + dataRx);
                if(dataRx!=IGNORE_00){
                 
                 stringToRx += (char)dataRx;
                 runOnUiThread(new Runnable(){

      @Override
      public void run() {
       textIn.setText(stringToRx);
      }});
                }
            } else {
                break;
            }
        }
  
 }

}

download filesDownload the files.


Arduino side:

UsbSlave.ino
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int pinLED = 13;

const int ST_0 = 0;      //waiting Sync word
const int ST_1_CMD = 1;  //Waiting CMD
const int ST_2_LENGTH= 2;//Receiving Length for CMD_03_TEXT
const int ST_3_DATA= 3;  //Receiving Data for CMD_03_TEXT
const byte IGNORE_00 = 0x00;
const byte SYNC_WORD = 0xFF;
const byte CMD_01_LEDON = 0x01;
const byte CMD_02_LEDOFF= 0x02;
const byte CMD_03_TEXT  = 0x03;
int cmdState;
int dataIndex;

const int MAX_LENGTH = 16;
byte data[MAX_LENGTH];
int dataLength;

void setup() {
  Serial.begin(9600);
  pinMode(pinLED, OUTPUT);
  digitalWrite(pinLED, LOW);
  
  lcd.begin(16, 2);
  lcd.print("USB Host Mode:");
}

void loop() {
  
  if(Serial.available()){
    //lcd.setCursor(0, 1);
    //lcd.print("                ");
    //delay(100);
    //lcd.setCursor(0, 1);
    while(Serial.available() > 0){
      int byteIn = Serial.read();
      //Serial.write(byteIn);
      //lcd.write(byteIn);
      cmdHandle(byteIn);
    }
  }
}

void cmdHandle(int incomingByte){
  
  //prevent from lost-sync
  if(incomingByte == SYNC_WORD){
    cmdState = ST_1_CMD;
    return;
  }
  if(incomingByte == IGNORE_00){
    return;
  }
  
  switch(cmdState){
    case ST_1_CMD:{
          switch(incomingByte){
            case CMD_01_LEDON:
                digitalWrite(pinLED, HIGH);
                break;
            case CMD_02_LEDOFF:
                digitalWrite(pinLED, LOW);
                break;
            case CMD_03_TEXT:
                for(int i=0; i < MAX_LENGTH; i++){
                  data[i] = 0;
                }
                lcd.setCursor(0, 1);
                lcd.print("                ");
                lcd.setCursor(0, 1);
            
                cmdState = ST_2_LENGTH;
                dataIndex = 0;
                break;
            default:
                cmdState = ST_0;
          }
        }
        break;
    case ST_2_LENGTH:{
        dataLength = incomingByte;
        if(dataLength > MAX_LENGTH){
          dataLength = MAX_LENGTH;
        }
        cmdState = ST_3_DATA;
        }
        break;
    case ST_3_DATA:{
          data[dataIndex] = incomingByte;
          dataIndex++;
          
          Serial.write(incomingByte);
          lcd.write(incomingByte);
          
          if(dataIndex==dataLength){
            cmdState = ST_0;
          }
        }
        break;
  }
  
}

Download HERE.

Connection of LCD in Arduino Uno:



- Bi-directional communication between Android and Arduino in USB Host Mode, example 1
- Bi-directional communication between Android and Arduino in USB Host Mode, example 3 - work with 8x8 LED Matrix

Related:
- More example of communication between Android to Arduino Uno, in USB Host Mode

Monday, September 22, 2014

Run Systrace in Android-Eclipse


The Systrace tool helps analyze the performance of your application by capturing and displaying execution times of your applications processes and other Android system processes. The tool combines data from the Android kernel such as the CPU scheduler, disk activity, and application threads to generate an HTML report that shows an overall picture of an Android device’s system processes for a given period of time.

The Systrace tool is particularly useful in diagnosing display problems where an application is slow to draw or stutters while displaying motion or animation. For more information on how to use Systrace, see Analyzing Display and Performance.


Keyboard shortcuts that are available while viewing a Systrace trace HTML report.


ListView to display photos, modified from GridView


This video show how to download, import from last post of GridView, and modify to ListView to display photos.



download filesDownload the files.

Sunday, September 21, 2014

Bi-directional communication between Android and Arduino in USB Host Mode, example 1

This example implement bi-directional communication in USB Host mode, between Android and Arduino Uno. It's a button on Android, used to turn on/off the on-board LED (pin 13) on Arduino Uno. And a potentiometer on Arduino side, used to control the SeekBar on Android.


This example reference MissileLauncher example in Android SDK, you can locate it in the folder /sdk/samples/android-14/USB/MissileLauncher.

For Android-to-Arduino, command of CMD_LED_ON(1) and CMD_LED_OFF(2) are used to turn ON/OFF the LED on Arduino board. For Arduino-to-Android, '0' will be sent between data (I don't know why), so I force the value sent in the range 1-63.

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

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk
        android:minSdkVersion="12"
        android:targetSdkVersion="19" />

    <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>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>

</manifest>

/res/xml/device_filter.xml, for Arduino Uno.
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- idVendor=2341, idProduct=0043 for Arduino Uno R3 -->
    <usb-device 
        vendor-id="9025" 
        product-id="0067" />
</resources>

/res/layout/activity_main.xml
<LinearLayout 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:orientation="vertical"
    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.example.androidusbhostarduino.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
        
    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="63"
        android:progress="0"/>
    
    <ToggleButton 
        android:id="@+id/arduinoled"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textOn="ON"
        android:textOff="OFF" />

</LinearLayout>

MainActivity.java
package com.example.androidusbhostarduino;

import java.nio.ByteBuffer;

import android.support.v7.app.ActionBarActivity;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.os.Bundle;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class MainActivity extends ActionBarActivity implements Runnable{

 private static final int CMD_LED_OFF = 2;
 private static final int CMD_LED_ON = 1;

 SeekBar bar;
 ToggleButton buttonLed;
 
 private UsbManager usbManager;
    private UsbDevice deviceFound;
    private UsbDeviceConnection usbDeviceConnection;
    private UsbInterface usbInterfaceFound = null;
 private UsbEndpoint endpointOut = null;
 private UsbEndpoint endpointIn = null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  bar = (SeekBar)findViewById(R.id.seekbar);
  buttonLed = (ToggleButton)findViewById(R.id.arduinoled);
  buttonLed.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView,
     boolean isChecked) {
    if(isChecked){
     sendCommand(CMD_LED_ON);
    }else{
     sendCommand(CMD_LED_OFF);
    }
   }});
  
  usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
 }

 @Override
    public void onResume() {
        super.onResume();

        Intent intent = getIntent();
        String action = intent.getAction();

        UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
        if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
            setDevice(device);
        } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            if (deviceFound != null && deviceFound.equals(device)) {
                setDevice(null);
            }
        }
    }
 
 private void setDevice(UsbDevice device) {
        usbInterfaceFound = null;
     endpointOut = null;
     endpointIn = null;

        for (int i = 0; i < device.getInterfaceCount(); i++) {         
   UsbInterface usbif = device.getInterface(i);

   UsbEndpoint tOut = null;
   UsbEndpoint tIn = null;

   int tEndpointCnt = usbif.getEndpointCount();
   if (tEndpointCnt >= 2) {
    for (int j = 0; j < tEndpointCnt; j++) {
     if (usbif.getEndpoint(j).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
      if (usbif.getEndpoint(j).getDirection() == UsbConstants.USB_DIR_OUT) {
       tOut = usbif.getEndpoint(j);
      } else if (usbif.getEndpoint(j).getDirection() == UsbConstants.USB_DIR_IN) {
       tIn = usbif.getEndpoint(j);
      }
     }
    }

    if (tOut != null && tIn != null) {
     // This interface have both USB_DIR_OUT
     // and USB_DIR_IN of USB_ENDPOINT_XFER_BULK
     usbInterfaceFound = usbif;
     endpointOut = tOut;
     endpointIn = tIn;
    }
   }

  }
        
        if (usbInterfaceFound == null) {
            return;
        }

        deviceFound = device;
        
        if (device != null) {
            UsbDeviceConnection connection = 
             usbManager.openDevice(device);
            if (connection != null && 
             connection.claimInterface(usbInterfaceFound, true)) {
                usbDeviceConnection = connection;
                Thread thread = new Thread(this);
                thread.start();

            } else {
                usbDeviceConnection = null;
            }
         }
    }
 
 private void sendCommand(int control) {
        synchronized (this) {

            if (usbDeviceConnection != null) {
                byte[] message = new byte[1];
                message[0] = (byte)control;
                usbDeviceConnection.bulkTransfer(endpointOut,
                  message, message.length, 0);
            }
        }
    }

 @Override
 public void run() {
  ByteBuffer buffer = ByteBuffer.allocate(1);
        UsbRequest request = new UsbRequest();
        request.initialize(usbDeviceConnection, endpointIn);
        while (true) {
            request.queue(buffer, 1);
            if (usbDeviceConnection.requestWait() == request) {
                byte rxCmd = buffer.get(0);
                if(rxCmd!=0){
                 bar.setProgress((int)rxCmd);
                }

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            } else {
                break;
            }
        }
  
 }

}

download filesDownload the files.

UsbSlave.ino, sketch on Arduino Uno
int prvValue;

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  
  prvValue = 0;
}

void loop() {
  if(Serial.available()){
    byte cmd = Serial.read();
    if(cmd == 0x02){
      digitalWrite(13, LOW);
    }else if(cmd == 0x01){
      digitalWrite(13, HIGH);
    }

  }
  
  int sensorValue = analogRead(A0) >> 4;
  byte dataToSent;
  if(prvValue != sensorValue){
    prvValue = sensorValue;
    
    if (prvValue==0x00){
      dataToSent = (byte)0x01;
    }else{
      dataToSent = (byte)prvValue;
    }
    
    Serial.write(dataToSent);
    delay(100);
  }

}

Download HERE.

A potentiometer is need:


Bi-directional communication between Android and Arduino in USB Host Mode, example 2

Related:
- More example of communication between Android to Arduino Uno, in USB Host Mode

Touch GridView to show photo

Further work on last exercise of GridView "Improve JPG loading with ExifInterface.getThumbnail()", to open select photo when any item clicked.



Create /res/layout/jpgdialog.xml to define the layout of dialog to show clicked photo.
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_root"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:paddingLeft="10dip"
    android:paddingRight="10dip" >

    <TextView
        android:id="@+id/textpath"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <ImageView
        android:id="@+id/image"
        android:layout_width="500dp"
        android:layout_height="350dp" />

    <Button
        android:id="@+id/okdialogbutton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="OK" />

</LinearLayout>

Modify MainActivity.java to open dialog to show selected photo. In order to make it simple, the loading of photos for dialog is implemented in UI thread.
package com.example.androidgridview;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import android.media.ExifInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;
 
 static final int ID_JPGDIALOG = 0;
 ImageView jpgdialigImage;
 TextView jpgdialigText;
 File jpgdialigFile;
 //have to match width and height of 
 //"@+id/image" in jpgdialog.xml
 final int DIALOG_IMAGE_WIDTH = 500;
 final int DIALOG_IMAGE_HEIGHT = 350;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   //open jpg only
   File[] files = targetDirector.listFiles(new FilenameFilter() {
       public boolean accept(File dir, String name)
       {
           return (name.endsWith(".jpg")||name.endsWith(".JPG")); 
       }
   });
   //File[] files = targetDirector.listFiles();
   
   Arrays.sort(files);
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

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

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  //getView load bitmap in AsyncTask
  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
   ViewHolder holder;

   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    
    convertView = imageView;
    
    holder = new ViewHolder();
    holder.image = imageView;
    holder.position = position;
    convertView.setTag(holder);
   } else {
    //imageView = (ImageView) convertView;
    holder = (ViewHolder) convertView.getTag();
    ((ImageView)convertView).setImageBitmap(null);
   }
   
   //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
   // Using an AsyncTask to load the slow images in a background thread
   new AsyncTask<ViewHolder, Void, Bitmap>() {
       private ViewHolder v;

       @Override
       protected Bitmap doInBackground(ViewHolder... params) {
        
        Bitmap bm = null;
        
        boolean haveThumbNail = false;
        
        try {
      ExifInterface exifInterface = 
       new ExifInterface(itemList.get(position));
      if(exifInterface.hasThumbnail()){
       byte[] thumbnail = exifInterface.getThumbnail();
       bm = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
      }
      haveThumbNail = true;
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
        
        if(!haveThumbNail){
         bm = decodeSampledBitmapFromUri(
           itemList.get(position), 220, 220);
        }

           v = params[0];
           return bm;
       }

       @Override
       protected void onPostExecute(Bitmap result) {
           super.onPostExecute(result);
           //Not work for me!
           /*
           if (v.position == position) {
               // If this item hasn't been recycled already, 
            // show the image
               v.image.setImageBitmap(result);
           }
           */

           v.image.setImageBitmap(result);

       }
   }.execute(holder);

   //imageView.setImageBitmap(bm);
   //return imageView;
   return convertView;
  }

  /*
  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public int calculateInSampleSize(

  BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;

   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }
  */
  
  class ViewHolder {
            ImageView image;
            int position;
        }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String path = (String) parent.getItemAtPosition(position);

   //Open dialog to show jpg
   jpgdialigFile = new File(path);
   
   showDialog(ID_JPGDIALOG);

  }
 };

 @Override
 @Deprecated
 protected Dialog onCreateDialog(int id) {
  final Dialog jpgDialog = new Dialog(this);
  switch(id){
  case ID_JPGDIALOG:

   jpgDialog.setContentView(R.layout.jpgdialog);
   jpgdialigImage = (ImageView)jpgDialog.findViewById(R.id.image);
   jpgdialigText = (TextView)jpgDialog.findViewById(R.id.textpath);
   
   Button okDialogButton = (Button)jpgDialog.findViewById(R.id.okdialogbutton);
   okDialogButton.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     jpgDialog.dismiss();
    }});
   break;
   
  default:
   break; 
  }
  
  return jpgDialog;
 }

 @Override
 @Deprecated
 protected void onPrepareDialog(int id, Dialog dialog) {
  switch(id){
  case ID_JPGDIALOG:
   jpgdialigText.setText(jpgdialigFile.getPath());
   //Bitmap bm = BitmapFactory.decodeFile(jpgdialigFile.getPath());
   Bitmap bm = decodeSampledBitmapFromUri(jpgdialigFile.getPath(), 
    DIALOG_IMAGE_WIDTH, DIALOG_IMAGE_HEIGHT);
   jpgdialigImage.setImageBitmap(bm);
   
   break;
   
  default:
   break;
   }
 }
 
 private Bitmap decodeSampledBitmapFromUri(
  String path, int reqWidth, int reqHeight) {
  
  Bitmap bm = null;
  // First decode with inJustDecodeBounds=true to check dimensions
  final BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;
  BitmapFactory.decodeFile(path, options);

  // Calculate inSampleSize
  options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

  // Decode bitmap with inSampleSize set
  options.inJustDecodeBounds = false;
  bm = BitmapFactory.decodeFile(path, options);

  return bm; 
 }
 
 public int calculateInSampleSize(
  BitmapFactory.Options options, int reqWidth, int reqHeight) {
  
  // Raw height and width of image
  final int height = options.outHeight;
  final int width = options.outWidth;
  int inSampleSize = 1;
   
  if (height > reqHeight || width > reqWidth) {
   if (width > height) {
    inSampleSize = Math.round((float) height/(float) reqHeight);  
   } else {
    inSampleSize = Math.round((float) width / (float) reqWidth);  
   }  
  }
  
  return inSampleSize; 
 }

}



download filesDownload the files.

- Modify this example to ListView

Friday, September 19, 2014

Java/JavaFX example: 1 server vs 30 clients

This video test how one Java/JavaFX server connected with 30 clients, all one on the same PC (running Linux) for testing.


reference:
Java/JavaFX Server link to Android Client
Java/JavaFX Client link to Android Server

Both server and client modified to suit for testing:

JavaFX_Server.java
package javafx_server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavaFX_Server extends Application {

    TextField textTitle;
    Label labelSys, labelPort, labelIp;
    TextArea textAreaMsg;
    CheckBox optWelcome;

    ServerSocket serverSocket;

    @Override
    public void start(Stage primaryStage) {

        textTitle = new TextField();
        labelSys = new Label();
        labelPort = new Label();
        labelIp = new Label();
        textAreaMsg = new TextArea();
        
        //Auto scroll to bottom
        textAreaMsg.textProperty().addListener(new ChangeListener(){

            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                textAreaMsg.setScrollTop(Double.MAX_VALUE); 
            }
        });
        
        optWelcome = new CheckBox("Send Welcome when connect");
        optWelcome.setSelected(true);

        labelSys.setText(
            System.getProperty("os.arch") + "/"
            + System.getProperty("os.name"));
        labelIp.setText(getIpAddress());

        VBox mainLayout = new VBox();
        mainLayout.setPadding(new Insets(5, 5, 5, 5));
        mainLayout.setSpacing(5);
        mainLayout.getChildren().addAll(textTitle,
            labelSys, labelPort, labelIp,
            optWelcome, textAreaMsg);

        StackPane root = new StackPane();
        root.getChildren().add(mainLayout);

        Scene scene = new Scene(root, 300, 400);

        primaryStage.setTitle("Android-er: JavaFX Server");
        primaryStage.setScene(scene);
        primaryStage.show();

        Thread socketServerThread = new Thread(new SocketServerThread());
        socketServerThread.setDaemon(true); //terminate the thread when program end
        socketServerThread.start();
    }

    public static void main(String[] args) {
        launch(args);
    }

    private class SocketServerThread extends Thread {

        static final int SocketServerPORT = 8080;
        int count = 0;

        @Override
        public void run() {
            try {
                Socket socket = null;
                
                serverSocket = new ServerSocket(SocketServerPORT);
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {
                        labelPort.setText("I'm waiting here: "
                            + serverSocket.getLocalPort());
                    }
                });

                while (true) {
                    socket = serverSocket.accept();
                    count++;
                    
                    //Start another thread 
                    //to prevent blocked by empty dataInputStream
                    Thread acceptedThread = new Thread(
                        new ServerSocketAcceptedThread(socket, count));
                    acceptedThread.setDaemon(true); //terminate the thread when program end
                    acceptedThread.start();

                }
            } catch (IOException ex) {
                Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        }

    }

    private class ServerSocketAcceptedThread extends Thread {

        Socket socket = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        int count;

        ServerSocketAcceptedThread(Socket s, int c) {
            socket = s;
            count = c;
        }

        @Override
        public void run() {
            try {
                dataInputStream = new DataInputStream(
                    socket.getInputStream());
                dataOutputStream = new DataOutputStream(
                    socket.getOutputStream());

                //If dataInputStream empty, 
                //this thread will be blocked by readUTF(),
                //but not the others
                String messageFromClient = dataInputStream.readUTF();

                String newMessage = "#" + count + " from " + socket.getInetAddress()
                    + ":" + socket.getPort() + "\n"
                    + "Msg from client: " + messageFromClient + "\n";
                
                Platform.runLater(new Runnable() {
                    
                    @Override
                    public void run() {
                        textAreaMsg.appendText(newMessage);
                    }
                });
                
                if (optWelcome.isSelected()) {
                    
                    String msgReply = count + ": " + textTitle.getText();
                    dataOutputStream.writeUTF(msgReply);
                }
                
            } catch (IOException ex) {
                Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }

                if (dataInputStream != null) {
                    try {
                        dataInputStream.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }

                if (dataOutputStream != null) {
                    try {
                        dataOutputStream.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }

    }

    private String getIpAddress() {
        String ip = "";
        try {
            Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
                .getNetworkInterfaces();
            while (enumNetworkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = enumNetworkInterfaces
                    .nextElement();
                Enumeration<InetAddress> enumInetAddress = networkInterface
                    .getInetAddresses();
                while (enumInetAddress.hasMoreElements()) {
                    InetAddress inetAddress = enumInetAddress.nextElement();

                    if (inetAddress.isSiteLocalAddress()) {
                        ip += "SiteLocalAddress: "
                            + inetAddress.getHostAddress() + "\n";
                    }
                }
            }
        } catch (SocketException ex) {
            Logger.getLogger(JavaFX_Server.class.getName()).log(Level.SEVERE, null, ex);
        } 

        return ip;
    }

}

Download.

JavaFX_Client.java
package javafx_client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavaFX_Client extends Application {

    Label labelSys;
    TextField welcomeMsg;
    Label labelAddress;
    TextField textAddress;
    Label labelPort;
    TextField textPort;
    Button buttonConnect;
    Button buttonClear;
    Label textResponse;

    @Override
    public void start(Stage primaryStage) {

        labelSys = new Label();
        welcomeMsg = new TextField();
        labelAddress = new Label("IP Address");
        textAddress = new TextField();
        labelPort = new Label("Port");
        textPort = new TextField();
        buttonConnect = new Button("Connect");
        buttonClear = new Button("Clear");
        textResponse = new Label();

        labelSys.setText(
                System.getProperty("os.arch") + "/"
                + System.getProperty("os.name"));

        HBox buttonbox = new HBox();
        buttonbox.setSpacing(5);
        HBox.setHgrow(buttonConnect, Priority.ALWAYS);
        HBox.setHgrow(buttonClear, Priority.ALWAYS);
        buttonConnect.setMaxWidth(Double.MAX_VALUE);
        buttonClear.setMaxWidth(Double.MAX_VALUE);
        buttonbox.getChildren().addAll(buttonConnect, buttonClear);

        buttonConnect.setOnAction(buttonConnectEventHandler);
        
        buttonClear.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                textResponse.setText("");
            }
        });

        VBox mainLayout = new VBox();
        mainLayout.setPadding(new Insets(5, 5, 5, 5));
        mainLayout.setSpacing(5);
        mainLayout.getChildren().addAll(labelSys, welcomeMsg,
                labelAddress, textAddress, labelPort, textPort,
                buttonbox, textResponse);

        StackPane root = new StackPane();
        root.getChildren().add(mainLayout);

        Scene scene = new Scene(root, 300, 400);

        String myPID = ManagementFactory.getRuntimeMXBean().getName();
        primaryStage.setTitle(myPID);
        primaryStage.setScene(scene);
        primaryStage.show();
        
        //Prepare and send preset setting
        String presetMsg = "Hello from " + myPID;
        welcomeMsg.setText(presetMsg);
        textAddress.setText("192.168.1.103");
        textPort.setText("8080");
        RunnableClient presetClient
            = new RunnableClient(textAddress.getText(), 
                    Integer.parseInt(textPort.getText()),
                    presetMsg);
            
        new Thread(presetClient).start();
        //---
    }

    public static void main(String[] args) {
        launch(args);
    }

    EventHandler<ActionEvent> buttonConnectEventHandler
        = new EventHandler<ActionEvent>() {

        @Override
        public void handle(ActionEvent event) {
            String tMsg = welcomeMsg.getText();
            if (tMsg.equals("")) {
                tMsg = null;
            }

            RunnableClient runnableClient
                = new RunnableClient(textAddress.getText(), 
                    Integer.parseInt(textPort.getText()),
                    tMsg);
            
            new Thread(runnableClient).start();
        }
    };

    class RunnableClient implements Runnable{
        
        String dstAddress;
        int dstPort;
        String response = "";
        String msgToServer;
        
        public RunnableClient(String addr, int port, String msgTo) {
            dstAddress = addr;
            dstPort = port;
            msgToServer = msgTo;
        }

        @Override
        public void run() {
            Socket socket = null;
            DataOutputStream dataOutputStream = null;
            DataInputStream dataInputStream = null;

            try {
                socket = new Socket(dstAddress, dstPort);
                dataOutputStream = new DataOutputStream(
                    socket.getOutputStream());
                dataInputStream = new DataInputStream(socket.getInputStream());

                if(msgToServer != null){
                    dataOutputStream.writeUTF(msgToServer);
                }
    
                response = dataInputStream.readUTF();
   

            } catch (IOException ex) { 
                Logger.getLogger(JavaFX_Client.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                
                Platform.runLater(new Runnable(){

                    @Override
                    public void run() {
                        textResponse.setText(response);
                    }
                    
                });
                
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Client.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                
                if (dataOutputStream != null) {
                    try {
                        dataOutputStream.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Client.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                
                if (dataInputStream != null) {
                    try {
                        dataInputStream.close();
                    } catch (IOException ex) {
                        Logger.getLogger(JavaFX_Client.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                
            }
        }
        
    }

}

Download.

For testing, you have to create a shell script and make it executable. to start 30 JavaFX_Client application.



Thursday, September 18, 2014

Improve JPG loading with ExifInterface.getThumbnail()

Refer to last example of "getView() to load images in AsyncTask", the loading of large images (such as photos from camera/DSLR) is too slow to accept. Most JPG embedd with thumbnail, we can use ExifInterface to check and load if it has thumbnail embedded.

Check this video to know how it improve the performance:


Modify MainActivity.java from last example of "getView() to load images in AsyncTask", accepet jpg only, and check and load thumbnail.

package com.example.androidgridview;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import android.media.ExifInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   //open jpg only
   File[] files = targetDirector.listFiles(new FilenameFilter() {
       public boolean accept(File dir, String name)
       {
           return (name.endsWith(".jpg")||name.endsWith(".JPG")); 
       }
   });
   //File[] files = targetDirector.listFiles();
   
   Arrays.sort(files);
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

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

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  //getView load bitmap in AsyncTask
  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
   ViewHolder holder;

   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    
    convertView = imageView;
    
    holder = new ViewHolder();
    holder.image = imageView;
    holder.position = position;
    convertView.setTag(holder);
   } else {
    //imageView = (ImageView) convertView;
    holder = (ViewHolder) convertView.getTag();
    ((ImageView)convertView).setImageBitmap(null);
   }
   
   //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
   // Using an AsyncTask to load the slow images in a background thread
   new AsyncTask<ViewHolder, Void, Bitmap>() {
       private ViewHolder v;

       @Override
       protected Bitmap doInBackground(ViewHolder... params) {
        
        Bitmap bm = null;
        
        boolean haveThumbNail = false;
        
        try {
      ExifInterface exifInterface = 
       new ExifInterface(itemList.get(position));
      if(exifInterface.hasThumbnail()){
       byte[] thumbnail = exifInterface.getThumbnail();
       bm = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
      }
      haveThumbNail = true;
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
        
        if(!haveThumbNail){
         bm = decodeSampledBitmapFromUri(
           itemList.get(position), 220, 220);
        }

           v = params[0];
           return bm;
       }

       @Override
       protected void onPostExecute(Bitmap result) {
           super.onPostExecute(result);
           //Not work for me!
           /*
           if (v.position == position) {
               // If this item hasn't been recycled already, 
            // show the image
               v.image.setImageBitmap(result);
           }
           */

           v.image.setImageBitmap(result);

       }
   }.execute(holder);

   //imageView.setImageBitmap(bm);
   //return imageView;
   return convertView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public int calculateInSampleSize(

  BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;

   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }
  
  class ViewHolder {
            ImageView image;
            int position;
        }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}

download filesDownload the files.

Next:
Touch GridView to show photo

Apply animation on buttons to start activity

Example to apply Animation on Buttons, and implement AnimationListener to startActivity() when onAnimationEnd() called. Notice that when a animation is running, the buttons still can accept click to start other animation, and create more AnimationListener to start more than one second activity when ended. (It's further work on former exercise "Apply animation on Button")


Create animation xml in /res/anim folder.

/res/anim/anim_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator">
    <alpha 
        android:fromAlpha="1.0" 
        android:toAlpha="0.1" 
        android:duration="500" 
        android:repeatCount="1"
        android:repeatMode="reverse" />
</set>

/res/anim/anim_rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator">
    <rotate
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="500"
        android:startOffset="0"
        android:repeatCount="1"
        android:repeatMode="reverse" />
</set>

/res/anim/anim_scale.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator">
    <scale
        android:fromXScale="1.0"
        android:toXScale="3.0"
        android:fromYScale="1.0"
        android:toYScale="3.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="500" 
        android:repeatCount="1"
        android:repeatMode="reverse" />
</set>

/res/anim/anim_translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator">
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%p"
        android:duration="500"
        android:repeatCount="1"
        android:repeatMode="reverse"/>
</set>

/res/layout/main.xml, layout of main activity(AndroidAnimButtonsActivity.java).
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/translate"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Translate" />
    <Button
        android:id="@+id/alpha"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Alpha" />
    <Button
        android:id="@+id/scale"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Scale" />
    <Button
        android:id="@+id/rotate"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Rotate" />

</LinearLayout>

AndroidAnimButtonsActivity.java
package com.exercise.AndroidAnimButtons;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.Button;

public class AndroidAnimButtonsActivity extends Activity {
 
 static String KEY_ANIM = "TARGET_ANIM";
 static String Target_Translate = "Translate";
 static String Target_Alpha = "Alpha";
 static String Target_Scale = "Scale";
 static String Target_Rotate = "Rotate";
 String target_op = Target_Translate; //dummy default 
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final Animation animTranslate = AnimationUtils.loadAnimation(this, R.anim.anim_translate);
        final Animation animAlpha = AnimationUtils.loadAnimation(this, R.anim.anim_alpha);
        final Animation animScale = AnimationUtils.loadAnimation(this, R.anim.anim_scale);
        final Animation animRotate = AnimationUtils.loadAnimation(this, R.anim.anim_rotate);
        
        Button btnTranslate = (Button)findViewById(R.id.translate);
        Button btnAlpha = (Button)findViewById(R.id.alpha);
        Button btnScale = (Button)findViewById(R.id.scale);
        Button btnRotate = (Button)findViewById(R.id.rotate);
        
        btnTranslate.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    target_op = Target_Translate;
    arg0.startAnimation(animTranslate);
   }});
        
        btnAlpha.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    target_op = Target_Alpha;
    arg0.startAnimation(animAlpha);
   }});
        
        btnScale.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    target_op = Target_Scale;
    arg0.startAnimation(animScale);
   }});
        
        btnRotate.setOnClickListener(new Button.OnClickListener(){

   @Override
   public void onClick(View arg0) {
    target_op = Target_Rotate;
    arg0.startAnimation(animRotate);
   }});
        
        animTranslate.setAnimationListener(animationListener);
        animAlpha.setAnimationListener(animationListener);
        animScale.setAnimationListener(animationListener);
        animRotate.setAnimationListener(animationListener);
    }
    
    AnimationListener animationListener = new AnimationListener(){

  @Override
  public void onAnimationStart(Animation animation) {
   // TODO Auto-generated method stub
   
  }

  @Override
  public void onAnimationEnd(Animation animation) {
   Intent intent = new Intent(
     AndroidAnimButtonsActivity.this, 
     SecondActivity.class);
        intent.putExtra(KEY_ANIM, target_op);
        startActivity(intent);
  }

  @Override
  public void onAnimationRepeat(Animation animation) {
   // TODO Auto-generated method stub
   
  }};
}

/res/layout/second.xml, layout of the second activity(SecondActivity.java).
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/anbutton"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Translate" />

</LinearLayout>

SecondActivity.java
package com.exercise.AndroidAnimButtons;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

public class SecondActivity extends Activity {

 Animation anim;
 Button anButton;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.second);
  Intent intent = getIntent();
  String op = intent.getStringExtra(AndroidAnimButtonsActivity.KEY_ANIM);
  if (op.equals(AndroidAnimButtonsActivity.Target_Translate)){
   anim = AnimationUtils.loadAnimation(SecondActivity.this, R.anim.anim_translate);
  }else if (op.equals(AndroidAnimButtonsActivity.Target_Alpha)){
   anim = AnimationUtils.loadAnimation(SecondActivity.this, R.anim.anim_alpha);
  }else if (op.equals(AndroidAnimButtonsActivity.Target_Scale)){
   anim = AnimationUtils.loadAnimation(SecondActivity.this, R.anim.anim_scale);
  }else if (op.equals(AndroidAnimButtonsActivity.Target_Rotate)){
   anim = AnimationUtils.loadAnimation(SecondActivity.this, R.anim.anim_rotate);
  }
  
  anButton = (Button)findViewById(R.id.anbutton);
  anButton.setText(op);
  
  anButton.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener(){

   @Override
   public void onGlobalLayout() {

    anButton.startAnimation(anim);
    
    //deprecated in API level 16
    anButton.getViewTreeObserver().removeGlobalOnLayoutListener(this);
       //for API Level >= 16
       //anImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
   }});
  
  anButton.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    anButton.startAnimation(anim);
   }});
 }

}

Modify AndroidManifest.xml to add SecondActivity.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exercise.AndroidAnimButtons"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AndroidAnimButtonsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>


download filesDownload the files.


How to enable emoji input on Android

Open Setting -> Language & input, scroll down to Keyboard & Input Method. If you see the option of iWnn IME (Emoji), that means your device built-in support Emoji input. Check to enable it.


When you enter text, press and hold SpaceBar, to switch to Emoji (iWnn IME).



Wednesday, September 17, 2014

Android One: Apni Kismat Apne Haath

getView() to load images in AsyncTask

This post show getView() loading images in AsyncTask, to make UI responsive. Re-call my old "GridView example: load images to GridView from SD Card in background", it load the files in backgroud, to make the GridView start-up faster; but not in loading images in getView. Such that if the GridView going to load large images, it will block the UI in getView() and make the UI unresponsive.


The Android tutorial "Making ListView Scrolling Smooth" show how to improve getView() by loading images in AsyncTask.

The page advise to check (v.position == position) to varify if this item hasn't been recycled already. But it not work in this case. (actually, I don't understand how is the logic) In my implementation, roll-out images will be shown on incorrect cell, but the final images will replace and correct it finally.

To load images in AsyncTask will not speed up your image loading, but improve UI response.


MainActivity.java
package com.example.androidgridview;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   File[] files = targetDirector.listFiles();
   Arrays.sort(files);
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

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

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  //getView load bitmap ui thread
  /*
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
   } else {
    imageView = (ImageView) convertView;
   }

   Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220,
     220);

   imageView.setImageBitmap(bm);
   return imageView;
  }
  */

  //getView load bitmap in AsyncTask
  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
   ViewHolder holder;

   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    
    convertView = imageView;
    
    holder = new ViewHolder();
    holder.image = imageView;
    holder.position = position;
    convertView.setTag(holder);
   } else {
    //imageView = (ImageView) convertView;
    holder = (ViewHolder) convertView.getTag();
    ((ImageView)convertView).setImageBitmap(null);
   }
   
   //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
   // Using an AsyncTask to load the slow images in a background thread
   new AsyncTask<ViewHolder, Void, Bitmap>() {
       private ViewHolder v;

       @Override
       protected Bitmap doInBackground(ViewHolder... params) {
           v = params[0];
           Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
           return bm;
       }

       @Override
       protected void onPostExecute(Bitmap result) {
           super.onPostExecute(result);
           //Not work for me!
           /*
           if (v.position == position) {
               // If this item hasn't been recycled already, 
            // show the image
               v.image.setImageBitmap(result);
           }
           */

           v.image.setImageBitmap(result);

       }
   }.execute(holder);

   //imageView.setImageBitmap(bm);
   //return imageView;
   return convertView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public int calculateInSampleSize(

  BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;

   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }
  
  class ViewHolder {
            ImageView image;
            int position;
        }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}

/res/layout/activity_main.xml
<LinearLayout 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:orientation="vertical">

    <Button
        android:id="@+id/reload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Reload"/>
    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="220dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>

Permission of "android.permission.READ_EXTERNAL_STORAGE" is needed to read storage.

download filesDownload the files.

Next:
Improve JPG loading with ExifInterface.getThumbnail()


Tuesday, September 16, 2014

Place groupIndicator of ExpandableListView on right side

To place groupIndicator of ExpandableListView on right side, we can call setIndicatorBounds() for Android devices before Jelly Bean, or call setIndicatorBoundsRelative() for devices of Jelly Bean or higher.

Run on Nexus 7 of Android 4.4.4:



On Nexus One running Android 2.3.6



Modify MainActivity.java of last example "Create groupIndicator for ExpandableListView example", override onWindowFocusChanged() method.

package com.example.androidexpandablelistview;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.support.v7.app.ActionBarActivity;
import android.annotation.TargetApi;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.widget.ExpandableListView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {

 ExpandableListView expandableListView;
 MyExpandableListAdapter myExpandableListAdapter;
 List<String> groupList;
 HashMap<String, List<String>> childMap;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  init();
  expandableListView = (ExpandableListView) findViewById(R.id.mylist);
  myExpandableListAdapter = new MyExpandableListAdapter(this, groupList, childMap);
  expandableListView.setAdapter(myExpandableListAdapter);

 }

 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  
  Drawable drawable_groupIndicator = 
   getResources().getDrawable(R.drawable.groupindicator);
  int drawable_width = drawable_groupIndicator.getMinimumWidth();
  
  if(android.os.Build.VERSION.SDK_INT < 
   android.os.Build.VERSION_CODES.JELLY_BEAN_MR2){
   expandableListView.setIndicatorBounds(
    expandableListView.getWidth()-drawable_width, 
    expandableListView.getWidth());
  }else{
   expandableListView.setIndicatorBoundsRelative(
    expandableListView.getWidth()-drawable_width, 
    expandableListView.getWidth());
  }
  
  Toast.makeText(MainActivity.this, 
   "drawable_width: " + drawable_width +"\n" +
   "expandableListView.getWidth()" + expandableListView.getWidth(), 
   Toast.LENGTH_LONG).show();
 }

 private void init() {
  groupList = new ArrayList<String>();
  childMap = new HashMap<String, List<String>>();

  List<String> groupList0 = new ArrayList<String>();
  groupList0.add("groupList0 - 1");
  groupList0.add("groupList0 - 2");
  groupList0.add("groupList0 - 3");
  
  List<String> groupList1 = new ArrayList<String>();
  groupList1.add("groupList1 - 1");
  groupList1.add("groupList1 - 2");
  groupList1.add("groupList1 - 3");

  List<String> groupList2 = new ArrayList<String>();
  groupList2.add("groupList2 - 1");
  groupList2.add("groupList2 - 2");
  groupList2.add("groupList2 - 3");

  List<String> groupList3 = new ArrayList<String>();
  groupList3.add("groupList3 - 1");
  groupList3.add("groupList3 - 2");
  groupList3.add("groupList3 - 3");

  groupList.add("Group List 0");
  groupList.add("Group List 1");
  groupList.add("Group List 2");
  groupList.add("Group List 3");

  childMap.put(groupList.get(0), groupList0);
  childMap.put(groupList.get(1), groupList1);
  childMap.put(groupList.get(2), groupList2);
  childMap.put(groupList.get(3), groupList3);
 }

}