Showing posts with label step-by-step series. Show all posts
Showing posts with label step-by-step series. Show all posts

Monday, June 6, 2016

Bluetooth LE Gatt Example, step-by-step

Last post demonstrate the Android Bluetooth Le Gatt example code with Arduino/Genuino 101. This post start to (TRY to) implement my own Bluetooth LE Gatt Example, step-by-step. The aim of the examples (in coming series) are scan BLE device, find and link with specified device Genuino 101 (programmed with "LEDCB", refer previous post), and read and write characteristic of the device to turn ON/OFF the Genuino 101 on-board LED.

The main reference material are:
- Android Developers Guide > Bluetooth Low Energy
- The Android example code - BluetoothLeGatt. (The example in last post)

In this first step, create a new project in Android Studio, named AndroidBleGatt.

In my example (in coming post) I will scan LE device using BluetoothLeScanner (Added in API level 21) instead of calling deprecated startLeScan() and stopLeScan() methods as in the BluetoothLeGatt example, so specify the Min Sdk Version of API 21.

Edit AndroidManifest.xml to add uses-feature of "android.hardware.bluetooth_le", uses-permission of "android.permission.BLUETOOTH", "android.permission.BLUETOOTH_ADMIN" and "android.permission.ACCESS_COARSE_LOCATION".

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androidblegatt">

    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

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

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

</manifest>

Edit MainActivity.java
package com.blogspot.android_er.androidblegatt;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothLeScanner mBluetoothLeScanner;

    private boolean mScanning;

    private static final int RQS_ENABLE_BLUETOOTH = 1;

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

        // Check if BLE is supported on the device.
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this,
                    "BLUETOOTH_LE not supported in this device!",
                    Toast.LENGTH_SHORT).show();
            finish();
        }

        getBluetoothAdapterAndLeScanner();

        // Checks if Bluetooth is supported on the device.
        if (mBluetoothAdapter == null) {
            Toast.makeText(this,
                    "bluetoothManager.getAdapter()==null",
                    Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

    }

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

        if (!mBluetoothAdapter.isEnabled()) {
            if (!mBluetoothAdapter.isEnabled()) {
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, RQS_ENABLE_BLUETOOTH);
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode == RQS_ENABLE_BLUETOOTH && resultCode == Activity.RESULT_CANCELED) {
            finish();
            return;
        }

        getBluetoothAdapterAndLeScanner();

        // Checks if Bluetooth is supported on the device.
        if (mBluetoothAdapter == null) {
            Toast.makeText(this,
                    "bluetoothManager.getAdapter()==null",
                    Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

    private void getBluetoothAdapterAndLeScanner(){
        // Get BluetoothAdapter and BluetoothLeScanner.
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
        mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

        mScanning = false;
    }
}



Up to here, the app simple check Bluetooth LE available and enable it. In next post, I will show how to scan available Bluetooth LE devices in range.

Next:
Bluetooth LE Gatt Example, scan BLE devices
Scan specified BLE devices with ScanFilter
Connect to Bluetooth LE device and display GATT Services



Sunday, January 17, 2016

Replace ActionBar with android.support.v7.widget.Toolbar

A Toolbar is a generalization of action bars for use within application layouts. While an action bar is traditionally part of an Activity's opaque window decor controlled by the framework, a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy. An application may choose to designate a Toolbar as the action bar for an Activity using the setSupportActionBar() method.

This example show how to replace ActionBar with android.support.v7.widget.Toolbar.


- Create a new project with Minimum SDK of API 16, select template of Empty Activity.

- Edit res/values/styles.xml to use style of "Theme.AppCompat.Light.NoActionBar".
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

- Add <android.support.v7.widget.Toolbar> to /res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.blogspot.android_er.androidtoolbar.MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="#FFA000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</RelativeLayout>


- Edit MainActivity.java to call setSupportActionBar(toolbar) method.
package com.blogspot.android_er.androidtoolbar;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
}


- To make the Toolbar fill the width of the layout, edit /res/values/dimens.xml to set both "activity_horizontal_margin" and "activity_vertical_margin" to 0dp.
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">0dp</dimen>
    <dimen name="activity_vertical_margin">0dp</dimen>
</resources>


This video show how-to in Android Studio, and how it shown on Android Emulator of API 16 and 23.


More:
Set title, subtitle and logo of Toolbar
Add OptionsMenu to Toolbar
Set image on Toolbar
- Implement checkable items in OptionsMenu of Toolbar
Add button inside Toolbar
Implement transparent status bar for android.support.v7.widget.Toolbar
- ToolBar with ProgressBar (with video show the steps to place ToolBar using Android Studio graphical Design Tool)


Tuesday, October 13, 2015

Android Studio template of Google Maps Activity

This video show how to create Android App with Maps using Android Studio template of Google Maps Activity.


- You have to generate Google Maps API key with the prepared link in values/google_maps_api.xml, replace the string of "google_maps_key", refer to the video.
- This key is generated using debug.keystore, if you want to generate release APK, you have to generate another key using release keystore.
- The key show in the video is generated using the debug.keystore in my development PC, it will not work on your PC. You have to geneerate yourself.


The key will be refered in AndroidManifest.xml, inside <meta-data> of "com.google.android.geo.API_KEY".

Also include <uses-permission> of "android.permission.ACCESS_FINE_LOCATION".


Check the file build.gradle, dependencies of compile 'com.google.android.gms:play-services:8.1.0' is added for you.


"com.google.android.gms.maps.SupportMapFragment" is used to display map on your app.  It's a wrapper around a view of a map to automatically handle the necessary life cycle needs.






Step-by-step of Android Google Maps Activity using Google Maps Android API v2, on Android Studio:
Display "Legal Notices" for Google Maps Android API v2 on Options Menu
Set map type for Google Maps Activity using Google Maps Android API v2
Add Marker to Google Map (Google Maps Android API v2)
- Initialize map in xml
Detect touch on GoogleMap, onMapClick() and onMapLongClick()
Make GoogleMap's marker draggabe and detect moving marker
Custom InfoWindow
Detect user click on InfoWindow, by implementing GoogleMap.OnInfoWindowClickListener()
Display StreetViewPanoramaView in DialogFragment, when user click on InfoWindow
- to be continue...

Friday, October 9, 2015

Android NFC example, to read tag info of RFID key and card


A simple Android example to read info of RFID tag (key and card in this demo) using NFC.

Edit src/main/AndroidManifest.xml to add <intent-filter> with action of "android.nfc.action.TAG_DISCOVERED" and category of "android.intent.category.DEFAULT", such that the app will be started when RFID tag place near NFC; if no other apps registered.

And add <uses-permission> of "android.permission.NFC", and <uses-feature> of "android.hardware.nfc"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androidnfctagdiscovered" >

    <uses-permission android:name="android.permission.NFC"/>
    <uses-feature android:name="android.hardware.nfc"
        android:required="true"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>


In MainActivity.java:

In onCreate():
Create NfcAdapter object by calling NfcAdapter.getDefaultAdapter(this). And check if NFC supported and enabled.

Override onResume():
Check if intent is ACTION_TAG_DISCOVERED. if yes, we can obtain a Tag object from the intent, by calling intent.getParcelableExtra(NfcAdapter.EXTRA_TAG).

package com.blogspot.android_er.androidnfctagdiscovered;

import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private NfcAdapter nfcAdapter;
    TextView textViewInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textViewInfo = (TextView)findViewById(R.id.info);

        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if(nfcAdapter == null){
            Toast.makeText(this,
                    "NFC NOT supported on this devices!",
                    Toast.LENGTH_LONG).show();
            finish();
        }else if(!nfcAdapter.isEnabled()){
            Toast.makeText(this,
                    "NFC NOT Enabled!",
                    Toast.LENGTH_LONG).show();
            finish();
        }
    }

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

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

        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
            Toast.makeText(this,
                    "onResume() - ACTION_TAG_DISCOVERED",
                    Toast.LENGTH_SHORT).show();

            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            if(tag == null){
                textViewInfo.setText("tag == null");
            }else{
                String tagInfo = tag.toString() + "\n";

                tagInfo += "\nTag Id: \n";
                byte[] tagId = tag.getId();
                tagInfo += "length = " + tagId.length +"\n";
                for(int i=0; i<tagId.length; i++){
                    tagInfo += Integer.toHexString(tagId[i] & 0xFF) + " ";
                }
                tagInfo += "\n";

                String[] techList = tag.getTechList();
                tagInfo += "\nTech List\n";
                tagInfo += "length = " + techList.length +"\n";
                for(int i=0; i<techList.length; i++){
                    tagInfo += techList[i] + "\n ";
                }

                textViewInfo.setText(tagInfo);
            }
        }else{
            Toast.makeText(this,
                    "onResume() : " + action,
                    Toast.LENGTH_SHORT).show();
        }

    }
}


layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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:padding="16dp"
    android:orientation="vertical"
    tools:context=".MainActivity">

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

    <TextView
        android:id="@+id/info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>




Tag info of RFID card

Tag info of RFID key
reference: http://developer.android.com/guide/topics/connectivity/nfc/nfc.html

Next:
Android NFC read MifareClassic RFID tag, with android.nfc.action.TECH_DISCOVERED
Android NFC: readBlock() for MifareClassic, to dump data in RFID tag

Friday, August 14, 2015

Face Detection with Google Play services, Mobile Vision API (with demo APK)

With the release of Google Play services 7.8, new Mobile Vision APIs was added, to include a new Face API that finds human faces in images and video. ~ source: Android Developers Blog - Face Detection in Google Play services.

Follow the code lab "Face Detection with the Mobile Vision API", you can create an App to process an image that is already present in your app, to detects faces.

In this exercise, I modify the code lab to add the feature to load photos using Intent of ACTION_GET_CONTENT, such that you can test it with your own photos. APK is available on the bottom of this post.


- Make sure you have Google Play Services 7.8 or higher installed on your testing devices (Check your installed Google Play Services version).

- Create your app of "Blank Activity", with Min Sdk Version of API 17 (Android 4.2.2) or higher.

- Add dependency for Google Play services in Android Studio Project, make sure you have Google Play Services version 26 or higher installed.

- Edit your AndroidManifest.xml to add the statement of <meta-data...>:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidfacedetection" >
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="com.google.android.gms.vision.DEPENDENCIES"
            android:value="face" />
    </application>

</manifest>


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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/title"
        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" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="18dp"
        android:text="Face Detection with the Mobile Vision API" />

    <Button
        android:id="@+id/btnLoad"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Photo"/>

    <Button
        android:id="@+id/btnDetectFace"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Detect Face" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imgview"/>

</LinearLayout>


com.example.androidfacedetection.MainActivity.java
package com.example.androidfacedetection;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.android.gms.vision.Frame;
import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.face.FaceDetector;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MainActivity extends AppCompatActivity {

    private static final int RQS_LOADIMAGE = 1;
    private Button btnLoad, btnDetFace;
    private ImageView imgView;
    private Bitmap myBitmap;

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

        btnLoad = (Button)findViewById(R.id.btnLoad);
        btnDetFace = (Button)findViewById(R.id.btnDetectFace);
        imgView = (ImageView)findViewById(R.id.imgview);

        btnLoad.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                startActivityForResult(intent, RQS_LOADIMAGE);
            }
        });

        btnDetFace.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                if(myBitmap == null){
                    Toast.makeText(MainActivity.this,
                            "myBitmap == null",
                            Toast.LENGTH_LONG).show();
                }else{
                    detectFace();
                    Toast.makeText(MainActivity.this,
                            "Done",
                            Toast.LENGTH_LONG).show();
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == RQS_LOADIMAGE
                && resultCode == RESULT_OK){

            if(myBitmap != null){
                myBitmap.recycle();
            }

            try {
                InputStream inputStream =
                        getContentResolver().openInputStream(data.getData());
                myBitmap = BitmapFactory.decodeStream(inputStream);
                inputStream.close();
                imgView.setImageBitmap(myBitmap);

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    /*
    reference:
    https://search-codelabs.appspot.com/codelabs/face-detection
     */
    private void detectFace(){

        //Create a Paint object for drawing with
        Paint myRectPaint = new Paint();
        myRectPaint.setStrokeWidth(5);
        myRectPaint.setColor(Color.RED);
        myRectPaint.setStyle(Paint.Style.STROKE);

        //Create a Canvas object for drawing on
        Bitmap tempBitmap = Bitmap.createBitmap(myBitmap.getWidth(), myBitmap.getHeight(), Bitmap.Config.RGB_565);
        Canvas tempCanvas = new Canvas(tempBitmap);
        tempCanvas.drawBitmap(myBitmap, 0, 0, null);

        //Detect the Faces
        FaceDetector faceDetector = new FaceDetector.Builder(getApplicationContext()).build();

        //!!!
        //Cannot resolve method setTrackingEnabled(boolean)
        //skip for now
        //faceDetector.setTrackingEnabled(false);

        Frame frame = new Frame.Builder().setBitmap(myBitmap).build();
        SparseArray<Face> faces = faceDetector.detect(frame);

        //Draw Rectangles on the Faces
        for(int i=0; i<faces.size(); i++) {
            Face thisFace = faces.valueAt(i);
            float x1 = thisFace.getPosition().x;
            float y1 = thisFace.getPosition().y;
            float x2 = x1 + thisFace.getWidth();
            float y2 = y1 + thisFace.getHeight();
            tempCanvas.drawRoundRect(new RectF(x1, y1, x2, y2), 2, 2, myRectPaint);
        }
        imgView.setImageDrawable(new BitmapDrawable(getResources(),tempBitmap));

    }
}


In my trial:
- The calling of faceDetector.setTrackingEnabled(false) report error of "Cannot resolve method setTrackingEnabled(boolean)"! So I skip it in this example.
(fixed in next post: "FaceDetector error: Cannot resolve method setTrackingEnabled(boolean)")

- It work on RedMi 2, running Android 4.4.4, with Google Play services version 7.8.99 installed.
- But cannot detect face on Nexus 7 2012 (WITHOUT front camera) running Android 5.1.1, with the same Google Play services version 7.8.99 installed! with warning of "FaceDetectorHandle﹕ Native face detector not yet available.  Reverting to no-op detection".

Test on RedMi 2, running Android 4.4.4, with Google Play services version 7.8.99 installed.


download filesDownload the files (Android Studio Format).

download filesYou can also Download the APK here, for test without coding.


Next:
FaceDetector error: Cannot resolve method setTrackingEnabled(boolean)
Google Play services Face Detection, get Landmarks (eyes, nose, etc.)
- Detect Smiling
Introducing Face Detection in the Google Vision APIs, from 100 Days of Google Dev

Friday, August 7, 2015

Add Android Design Support Library to Android Studio Project

TO add Android Design Support Library to Android Studio Project, follow the steps here shown in the video:
- In Android Studio > File > Project Structure
- Select App -> Dependencies
- Select Design Support Library, 'com.android.support:design:22.2.1' currently.
- Click on the '+' symbol to add the libraries
- OK


Alternatively, you can also edit the file "build.gradle" manually.



Related:
- About Android Design Support Library
- Android Snackbar example of Android Design Support Library
Android FloatingActionButton example
CoordinatorLayout + FloatingActionButton + Snackbar
Set text and background color of Snackbar
Updated Android Studio now provide template of Blank Activity with FloatingActionButton and Snackbar


BottomSheet
BottomSheetDialog

Wednesday, July 8, 2015

Simple RecyclerView example

It's a simple example of using RecyclerView to display a List in vertical and horizontal.



To use RecyclerView in your Android Studio project, you have to Add Support Libraries of RecyclerView as dependencies.

Create our custom Adapter and ViewHolder for RecyclerView, com.example.androidrecyclerview.RecyclerViewAdapter.java
package com.example.androidrecyclerview;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ItemHolder> {

    private List<String> itemsName;
    private OnItemClickListener onItemClickListener;
    private LayoutInflater layoutInflater;

    public RecyclerViewAdapter(Context context){
        layoutInflater = LayoutInflater.from(context);
        itemsName = new ArrayList<String>();
    }

    @Override
    public RecyclerViewAdapter.ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = layoutInflater.inflate(R.layout.layout_item, parent, false);
        return new ItemHolder(itemView, this);
    }

    @Override
    public void onBindViewHolder(RecyclerViewAdapter.ItemHolder holder, int position) {
        holder.setItemName(itemsName.get(position));
    }

    @Override
    public int getItemCount() {
        return itemsName.size();
    }

    public void setOnItemClickListener(OnItemClickListener listener){
        onItemClickListener = listener;
    }

    public OnItemClickListener getOnItemClickListener(){
        return onItemClickListener;
    }

    public interface OnItemClickListener{
        public void onItemClick(ItemHolder item, int position);
    }

    public void add(int location, String iName){
        itemsName.add(location, iName);
        notifyItemInserted(location);
    }

    public void remove(int location){
        if(location >= itemsName.size())
            return;

        itemsName.remove(location);
        notifyItemRemoved(location);
    }

    public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        private RecyclerViewAdapter parent;
        TextView textItemName;

        public ItemHolder(View itemView, RecyclerViewAdapter parent) {
            super(itemView);
            itemView.setOnClickListener(this);
            this.parent = parent;
            textItemName = (TextView) itemView.findViewById(R.id.item_name);
        }

        public void setItemName(CharSequence name){
            textItemName.setText(name);
        }

        public CharSequence getItemName(){
            return textItemName.getText();
        }

        @Override
        public void onClick(View v) {
            final OnItemClickListener listener = parent.getOnItemClickListener();
            if(listener != null){
                listener.onItemClick(this, getPosition());
            }
        }
    }
}


And the layout for each cell in the RecyclerView, layout/layout_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/item_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="40dp"/>

</LinearLayout>


com.example.androidrecyclerview.MainActivity.java
package com.example.androidrecyclerview;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity implements RecyclerViewAdapter.OnItemClickListener{

    private RecyclerView myRecyclerView;
    private LinearLayoutManager linearLayoutManager;
    private RecyclerViewAdapter myRecyclerViewAdapter;

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

        myRecyclerView = new RecyclerView(this);

        linearLayoutManager =
                new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        /*
        linearLayoutManager =
                new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        */

        myRecyclerViewAdapter = new RecyclerViewAdapter(this);
        myRecyclerViewAdapter.setOnItemClickListener(this);
        myRecyclerView.setAdapter(myRecyclerViewAdapter);
        myRecyclerView.setLayoutManager(linearLayoutManager);

        setContentView(myRecyclerView);

        //insert dummy items
        myRecyclerViewAdapter.add(0, "Eric");
        myRecyclerViewAdapter.add(1, "Android");
        myRecyclerViewAdapter.add(0, "Android-er");
        myRecyclerViewAdapter.add(2, "Google");
        myRecyclerViewAdapter.add(3, "android dev");
        myRecyclerViewAdapter.add(0, "android-er.blogspot.com");
        myRecyclerViewAdapter.add(4, "Peter");
        myRecyclerViewAdapter.add(4, "Paul");
        myRecyclerViewAdapter.add(4, "Mary");
        myRecyclerViewAdapter.add(4, "May");
        myRecyclerViewAdapter.add(4, "Divid");
        myRecyclerViewAdapter.add(4, "Frankie");
    }

    @Override
    public void onItemClick(RecyclerViewAdapter.ItemHolder item, int position) {
        Toast.makeText(this,
                position + " : " + item.getItemName(),
                Toast.LENGTH_SHORT).show();
    }
}


download filesDownload the files (Android Studio Format).


Android RecyclerView examples (step-by-step):


Add Support Libraries of RecyclerView, CardView to Android Studio Project
- Simple RecyclerView example (this post)
Place RecyclerView in layout XML
Add and remove items to RecyclerView, with default animation
Implement custom ItemDecoration for RecyclerView
LinearLayoutManager and GridLayoutManager for RecyclerView
set SpanSizeLookup to GridLayoutManager of RecyclerView
onDraw() and onDrawOver() of ItemDecoration for RecyclerView
- Example using RecyclerView with CardView
StaggeredGridLayoutManager (Google+ App-like) on RecyclerView
RecyclerView + CardView example with ImageView - list available Drawable
RecyclerView + CardView example - list system properties using System.getProperties()
Intent.ACTION_OPEN_DOCUMENT to load images in RecyclerView + CardView
Gallery-like RecyclerView + CardView example
RecyclerView + CardView example: with Button
SwipeRefreshLayout, work with RecyclerView

Wednesday, October 22, 2014

Load Here map image, using REST API

This example show how to load Here map image, using REST API (not Native SDK). HERE, a Nokia company, is a global leader in the mapping and location intelligence business.



The Here Map Image API Developer's Quick Start Guide provide information to help you start using the Map Image API.

app_id and app_code are authentication credentials. You can use the demo credentials in the examples for testing, but must substitute them with your own unique values in your website or application. See Acquiring Credentials for more information.

MainActivity.java
package com.example.androidhererestmapimage;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import android.support.v7.app.ActionBarActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends ActionBarActivity {
 
 public class LoadHereMapTask extends AsyncTask<URL, Void, Bitmap> {
  
  ImageView imageView;
  
  LoadHereMapTask(ImageView v){
   imageView = v;
  }

  @Override
  protected Bitmap doInBackground(URL... params) {
   Bitmap bm = null;
   URL urlMapImage = params[0];
   try {
    bm = BitmapFactory.decodeStream(urlMapImage.openConnection().getInputStream());
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   return bm;
  }

  @Override
  protected void onPostExecute(Bitmap result) {
   imageView.setImageBitmap(result);
  }

 }

 ImageView mapImage;
 LoadHereMapTask loadHereMapTask;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mapImage = (ImageView)findViewById(R.id.mapimage);
  
  loadHereMapTask = new LoadHereMapTask(mapImage);
  
  URL urlTarget;
  try {
   urlTarget = new URL(genHereMapImageRequest());
   loadHereMapTask.execute(urlTarget);
  } catch (MalformedURLException e) {
   e.printStackTrace();
  }
  
 }
 
 private String genHereMapImageRequest(){
  
  String BaseURL = "http://image.maps.cit.api.here.com";
  String Path = "/mia/1.6/";
  String Resource = "mapview";
  String ApplicationId = "DemoAppId01082013GAL";  //for demo
  String ApplicationCode = "AJKnXv84fjrb0KIHawS0Tg"; //for demo
  String location = "52.378,13.520";     //Berlin  
  
  String rqs = BaseURL + Path + Resource 
    + "?app_id=" + ApplicationId
    + "&app_code=" + ApplicationCode
    + "&c=" + location;

  return rqs;
 }

}

/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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidhererestmapimage.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" />
    <ImageView
        android:id="@+id/mapimage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

uses-permission of "android.permission.INTERNET" is needed in AndroidManifest.xml.

download filesDownload the files.

More examples:
Here REST API example, set zoom level
Using Here Map Tile REST API on Android
- Base Map, Aerial and Traffic Tile
Using Here Geocoder API on Android
Search and display Map Tile using Here REST API

Wednesday, August 13, 2014

Display Google Charts (pie chart) on Android WebView

This example show how to use Google Charts to display pie chart on Android WebView. Google chart tools are powerful, simple to use, and free. It not only display a static graphic, but also provide user touch interactive operation, check the video on the bottom.

Display Google Charts (pie chart) displayed Android WebView
We have a MainActivity to collect data from user:
package com.example.androidwebchart;

import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;


public class MainActivity extends ActionBarActivity {
 
 EditText num1, num2, num3, num4, num5;
 Button btnShow;

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

        num1 = (EditText)findViewById(R.id.num1);
        num2 = (EditText)findViewById(R.id.num2);
        num3 = (EditText)findViewById(R.id.num3);
        num4 = (EditText)findViewById(R.id.num4);
        num5 = (EditText)findViewById(R.id.num5);
        btnShow = (Button)findViewById(R.id.show);
        
        btnShow.setOnClickListener(btnShowOnClickListener);
    }
    
    OnClickListener btnShowOnClickListener =
     new OnClickListener(){

   @Override
   public void onClick(View v) {
    Intent intent = new Intent(
     MainActivity.this, 
     ShowWebChartActivity.class);
    
    intent.putExtra("NUM1", getNum(num1));
    intent.putExtra("NUM2", getNum(num2));
    intent.putExtra("NUM3", getNum(num3));
    intent.putExtra("NUM4", getNum(num4));
    intent.putExtra("NUM5", getNum(num5));

    startActivity(intent);
   }
     
    };
    
    private int getNum(EditText editText){
     
     int num = 0;

     String stringNum = editText.getText().toString();
     if(!stringNum.equals("")){
      num = Integer.valueOf(stringNum);
     }
     
     return (num);
    }

}

/res/layout/activity_main.xml, layout of MainActivity.
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidwebchart.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" />
    <EditText
        android:id="@+id/num1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <EditText
        android:id="@+id/num2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <EditText
        android:id="@+id/num3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <EditText
        android:id="@+id/num4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <EditText
        android:id="@+id/num5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <Button
        android:id="@+id/show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Show Chart"/>

</LinearLayout>

When User click on the "Show Chart" button, it will start another activity, ShowWebChartActivity.java, and pass user data. ShowWebChartActivity load a WebView with our HTML to display Google Charts with Javascript. We have to implement WebAppInterface, with methods of @JavascriptInterface, getNum1()...getNum5(). It will be called by Javascript inside HTML to retrieve user data.
package com.example.androidwebchart;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;

@SuppressLint("SetJavaScriptEnabled") 
public class ShowWebChartActivity extends ActionBarActivity {
 
 WebView webView;
 int num1, num2, num3, num4, num5;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_webchart);
        
        Intent intent = getIntent();
        num1 = intent.getIntExtra("NUM1", 0);
        num2 = intent.getIntExtra("NUM2", 0);
        num3 = intent.getIntExtra("NUM3", 0);
        num4 = intent.getIntExtra("NUM4", 0);
        num5 = intent.getIntExtra("NUM5", 0);
        
        webView = (WebView)findViewById(R.id.web);
        webView.addJavascriptInterface(new WebAppInterface(), "Android");

        webView.getSettings().setJavaScriptEnabled(true); 
        webView.loadUrl("file:///android_asset/chart.html");
 }

 public class WebAppInterface {

     @JavascriptInterface
  public int getNum1() {
   return num1;
  }
  
  @JavascriptInterface
  public int getNum2() {
   return num2;
  }
  
  @JavascriptInterface
  public int getNum3() {
   return num3;
  }
  
  @JavascriptInterface
  public int getNum4() {
   return num4;
  }
  
  @JavascriptInterface
  public int getNum5() {
   return num5;
  }
 }

}

/res/layout/layout_webchart.xml, layout of ShowWebChartActivity.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_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" />
    
    <WebView
        android:id="@+id/web"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

/assets/chart.html, our HTML to load and display Google Charts. Actually it is modified from Google Quick Start example of pie chart. The main difference is it retrieve user data by calling Android JavascriptInterface methods (getNum1()...getNum5()), instead of fixed data.
<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
    
      // Load the Visualization API and the piechart package.
      google.load('visualization', '1.0', {'packages':['corechart']});
      
      // Set a callback to run when the Google Visualization API is loaded.
      google.setOnLoadCallback(drawChart);


      // Callback that creates and populates a data table, 
      // instantiates the pie chart, passes in the data and
      // draws it.
      function drawChart() {

      // Create the data table.
      var data = new google.visualization.DataTable();
      data.addColumn('string', 'ITEM');
      data.addColumn('number', 'VALUE');
      data.addRows([
        ['Item 1', Android.getNum1()],
        ['Item 2', Android.getNum2()],
        ['Item 3', Android.getNum3()], 
        ['Item 4', Android.getNum4()],
        ['Item 5', Android.getNum5()]
      ]);

      // Set chart options
      var options = {'title':'Android-er: Load Google Charts (pie chart) with WebView',
                     'width':600,
                     'height':600};

      // Instantiate and draw our chart, passing in some options.
      var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
      chart.draw(data, options);
    }
    </script>
  </head>

  <body>
 <!--Div that will hold the pie chart-->
    <div id="chart_div" style="width:600; height:600"></div>
  </body>
</html>

Finally, modify AndroidManifest.xml to add <activity> of ShowWebChartActivity, and <uses-permission> of "android.permission.INTERNET".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidwebchart"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET"/>

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

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

</manifest>


download filesDownload the files.

- More Google Charts examples on Android WebView
Capture image generated by Google Charts API
Display Donut Chart on Android WebView, using Google Charts

Wednesday, August 6, 2014

Bi-directional communication between Client and Server, using ServerSocket, Socket, DataInputStream and DataOutputStream

This example show bi-directional communication between Client and Server, both run on Android devices.

  • When the Server run, it will show it's own IP and port, open a ServerSocket and wait for socket connection from clients. 
  • In Client side, enter message to be sent to server, enter the server Ip and port, then clieck Connect... button, The client will connect to server using socket with DataInputStream and DataOutputStream loaded with message to send. 
  • In client side, when serverSocket.accept(), it will retrieve the message sent with dataInputStream.readUTF().
Both client and server need permission of "android.permission.INTERNET" in AndroidManifest.xml.

Notice:
- All code for network operation should run in background thread.
- The code dataInputStream.readUTF() will block the program flow if no data input. (Read Pervent program blocked by DataInputStream.readUTF())


Example code in Server Side:
package com.example.androidserversocket;

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 android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView info, infoip, msg;
 String message = "";
 ServerSocket serverSocket;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  info = (TextView) findViewById(R.id.info);
  infoip = (TextView) findViewById(R.id.infoip);
  msg = (TextView) findViewById(R.id.msg);

  infoip.setText(getIpAddress());

  Thread socketServerThread = new Thread(new SocketServerThread());
  socketServerThread.start();
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();

  if (serverSocket != null) {
   try {
    serverSocket.close();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

 private class SocketServerThread extends Thread {

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

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

   try {
    serverSocket = new ServerSocket(SocketServerPORT);
    MainActivity.this.runOnUiThread(new Runnable() {

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

    while (true) {
     socket = serverSocket.accept();
     dataInputStream = new DataInputStream(
       socket.getInputStream());
     dataOutputStream = new DataOutputStream(
       socket.getOutputStream());

     String messageFromClient = "";
     
     //If no message sent from client, this code will block the program
     messageFromClient = dataInputStream.readUTF();
     
     count++;
     message += "#" + count + " from " + socket.getInetAddress()
       + ":" + socket.getPort() + "\n"
       + "Msg from client: " + messageFromClient + "\n";

     MainActivity.this.runOnUiThread(new Runnable() {

      @Override
      public void run() {
       msg.setText(message);
      }
     });

     String msgReply = "Hello from Android, you are #" + count;
     dataOutputStream.writeUTF(msgReply);

    }
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    final String errMsg = e.toString();
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      msg.setText(errMsg);
     }
    });
    
   } finally {
    if (socket != null) {
     try {
      socket.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataInputStream != null) {
     try {
      dataInputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataOutputStream != null) {
     try {
      dataOutputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
   }
  }

 }

 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 e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   ip += "Something Wrong! " + e.toString() + "\n";
  }

  return ip;
 }
}

Layout:
<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=".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" />

    <TextView
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/infoip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <TextView
            android:id="@+id/msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>


download filesDownload the files.


Example code in Client Side:
package com.example.androidclient;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

 TextView textResponse;
 EditText editTextAddress, editTextPort;
 Button buttonConnect, buttonClear;
 
 EditText welcomeMsg;

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

  editTextAddress = (EditText) findViewById(R.id.address);
  editTextPort = (EditText) findViewById(R.id.port);
  buttonConnect = (Button) findViewById(R.id.connect);
  buttonClear = (Button) findViewById(R.id.clear);
  textResponse = (TextView) findViewById(R.id.response);
  
  welcomeMsg = (EditText)findViewById(R.id.welcomemsg);

  buttonConnect.setOnClickListener(buttonConnectOnClickListener);

  buttonClear.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    textResponse.setText("");
   }
  });
 }

 OnClickListener buttonConnectOnClickListener = new OnClickListener() {

  @Override
  public void onClick(View arg0) {
   
   String tMsg = welcomeMsg.getText().toString();
   if(tMsg.equals("")){
    tMsg = null;
    Toast.makeText(MainActivity.this, "No Welcome Msg sent", Toast.LENGTH_SHORT).show();
   }
   
   MyClientTask myClientTask = new MyClientTask(editTextAddress
     .getText().toString(), Integer.parseInt(editTextPort
     .getText().toString()),
     tMsg);
   myClientTask.execute();
  }
 };

 public class MyClientTask extends AsyncTask<Void, Void, Void> {

  String dstAddress;
  int dstPort;
  String response = "";
  String msgToServer;

  MyClientTask(String addr, int port, String msgTo) {
   dstAddress = addr;
   dstPort = port;
   msgToServer = msgTo;
  }

  @Override
  protected Void doInBackground(Void... arg0) {

   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 (UnknownHostException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    response = "UnknownHostException: " + e.toString();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    response = "IOException: " + e.toString();
   } finally {
    if (socket != null) {
     try {
      socket.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataOutputStream != null) {
     try {
      dataOutputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataInputStream != null) {
     try {
      dataInputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
   }
   return null;
  }

  @Override
  protected void onPostExecute(Void result) {
   textResponse.setText(response);
   super.onPostExecute(result);
  }

 }

}

Layout:
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".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" />
    <EditText 
        android:id="@+id/welcomemsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Say hello to server" />
    <EditText 
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="dstAddress" />
    <EditText 
        android:id="@+id/port"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="dstPort" />
    <Button 
        android:id="@+id/connect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Connect..."/>
    <Button 
        android:id="@+id/clear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Clear"/>
    <TextView
        android:id="@+id/response"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>


download filesDownload the files.


Client and Server run on WiFi share hotspot:

The following video, at 1:22, show it work when both client and server connect to the shared WiFi network from server hotspot.



Next:
Pervent program blocked by DataInputStream.readUTF()
Editable message sent from server
Java/JavaFX Client link to Android Server
Java/JavaFX Client run on Raspberry Pi, link with Android Server
Java/JavaFX Server link to Android Client
- A Simple Chat App

Friday, May 23, 2014

Draw Path on canvas of custom View

This example show drawing Path of circle on canvas of custom View, user can adjust its ratio using twh SeekBar. It will be the base of coming exercises.


MainActivity.java
package com.example.androiddrawpath;

import android.app.Activity;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

public class MainActivity extends Activity {

 SeekBar radiusBar;
 MyView myView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  radiusBar = (SeekBar) findViewById(R.id.radiusbar);
  myView = (MyView) findViewById(R.id.myview);
  float defaultRatio = (float) (radiusBar.getProgress())
    / (float) (radiusBar.getMax());
  myView.setShapeRadiusRatio(defaultRatio);

  radiusBar.setOnSeekBarChangeListener(radiusBarOnSeekBarChangeListener);

 };

 OnSeekBarChangeListener radiusBarOnSeekBarChangeListener = 
  new OnSeekBarChangeListener() {

  @Override
  public void onProgressChanged(SeekBar seekBar, int progress,
    boolean fromUser) {
   float defaultRatio = (float) (radiusBar.getProgress())
     / (float) (radiusBar.getMax());
   myView.setShapeRadiusRatio(defaultRatio);
   myView.invalidate();
  }

  @Override
  public void onStartTrackingTouch(SeekBar seekBar) {
  }

  @Override
  public void onStopTrackingTouch(SeekBar seekBar) {
  }

 };

}

MyView.java, our custom View.
package com.example.androiddrawpath;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path.Direction;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
 
 MyShape myShape;
 float ratioRadius;

 public MyView(Context context) {
  super(context);
  initMyView();
 }

 public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  initMyView();
 }

 public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  initMyView();
 }
 
 public void initMyView(){
  myShape = new MyShape();
 }
 
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  
  int w = getWidth();
  int h = getHeight();
  
  if((w==0) || (h==0)){
   return;
  }
  
  float x = (float)w/2.0f;
  float y = (float)h/2.0f;
  float radius;
  if(w > h){
   radius = h * ratioRadius;
  }else{
   radius = w * ratioRadius;
  }
  
  myShape.setCircle(x, y, radius, Direction.CCW);
  canvas.drawPath(myShape.getPath(), myShape.getPaint());
 }
 
 public void setShapeRadiusRatio(float ratio){
  ratioRadius = ratio;
 }

}

MyShape.java, it is the object hold the Path and Paint to be drawn on our View.
package com.example.androiddrawpath;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;

public class MyShape {

 private Paint paint;
 private Path path;

 public MyShape() {
  paint = new Paint();
  paint.setColor(Color.BLUE);
  paint.setStrokeWidth(3);
  paint.setStyle(Paint.Style.STROKE);
  
  path = new Path();
 }

 public void setCircle(float x, float y, float radius, Path.Direction dir){
  path.reset();
  path.addCircle(x, y, radius, dir);
 }
 
 public Path getPath(){
  return path;
 }
 
 public Paint getPaint(){
  return paint;
 }
 
}

Layout, /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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androiddrawpath.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" />
    
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="radius(%)"/>
    <SeekBar 
        android:id="@+id/radiusbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="50" />
    
    <com.example.androiddrawpath.MyView 
        android:id="@+id/myview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

download filesDownload the files.


More example:



Wednesday, May 7, 2014

Using Intent.ACTION_OPEN_DOCUMENT, for KitKat API 19 or higher

Android 4.4 KitKat, API 19, introduce new Intent.ACTION_OPEN_DOCUMENT, allow the user to select and return one or more existing documents. When invoked, the system will display the various DocumentsProvider instances installed on the device, letting the user interactively navigate through them. These documents include local media, such as photos and video, and documents provided by installed cloud storage providers.

Each document is represented as a content://

This example show how to select image file by calling startActivityForResult() with Intent of ACTION_OPEN_DOCUMENT, then load it in ImageView from the returned Uri.
  • To load image Intent.ACTION_OPEN_DOCUMENT, setType("image/*")
  • This example haven't handle resize of the image, so may be fail if load ImageView with large image.
  • If the app run on pre-KitKat devices, it will use old Intent.ACTION_GET_CONTENT.


package com.example.androidkitkatdocument;

import java.io.FileNotFoundException;

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

public class MainActivity extends Activity {

 private static final int RQS_OPEN_IMAGE = 1;

 Button buttonOpen;
 TextView textUri;
 ImageView imageView;
 
 Uri targetUri = null;

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

  buttonOpen = (Button) findViewById(R.id.opendocument);
  buttonOpen.setOnClickListener(buttonOpenOnClickListener);
  
  textUri = (TextView) findViewById(R.id.texturi);
  textUri.setOnClickListener(textUriOnClickListener);
  
  imageView = (ImageView)findViewById(R.id.image);
 }

 OnClickListener buttonOpenOnClickListener = 
  new OnClickListener() {

  @TargetApi(Build.VERSION_CODES.KITKAT)
  @Override
  public void onClick(View v) {
   Intent intent = new Intent();

   if (Build.VERSION.SDK_INT >= 
     Build.VERSION_CODES.KITKAT) {
    intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
   } else {
    intent.setAction(Intent.ACTION_GET_CONTENT);
   }

   intent.addCategory(Intent.CATEGORY_OPENABLE);

   // set MIME type for image
   intent.setType("image/*");

   startActivityForResult(intent, RQS_OPEN_IMAGE);
  }

 };
 
 OnClickListener textUriOnClickListener = 
  new OnClickListener(){

  @Override
  public void onClick(View v) {
   if (targetUri != null){
    Bitmap bm;
    try {
     bm = BitmapFactory.decodeStream(
      getContentResolver()
      .openInputStream(targetUri));
     imageView.setImageBitmap(bm);
    } catch (FileNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
  }
  
 };

 @TargetApi(Build.VERSION_CODES.KITKAT)
 @Override
 protected void onActivityResult(int requestCode, 
   int resultCode, Intent data) {

  if (resultCode == Activity.RESULT_OK) {

   Uri dataUri = data.getData();

   if (requestCode == RQS_OPEN_IMAGE) {
    targetUri = dataUri;
    textUri.setText(dataUri.toString());
   }
  }

 }

}

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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidkitkatdocument.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" />
    
    <Button 
        android:id="@+id/opendocument"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Document of Image" />
    
    <TextView
        android:id="@+id/texturi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <ImageView
        android:id="@+id/image"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

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

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

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

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

</manifest>


download filesDownload the files.

More examples:
Using Intent.ACTION_OPEN_DOCUMENT with EXTRA_MIME_TYPES to open file of multi mime types
Open multi files using Intent.ACTION_OPEN_DOCUMENT, with EXTRA_ALLOW_MULTIPLE and getClipData()
Intent.ACTION_OPEN_DOCUMENT to load images in RecyclerView + CardView
Hello World to open photo using Intent.ACTION_OPEN_DOCUMENT, with FloatingActionButton and Snackbar
Read Exif tag of JPG using ExifInterface(FileDescriptor)