AndroidHive | Tutorials, Games, Apps, Tips http://www.androidhive.info Thu, 18 Dec 2014 11:25:08 +0000 en-US hourly 1 http://wordpress.org/?v=3.9.1 Android Uploading Camera Image, Video to Server with Progress Barhttp://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/ http://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/#comments Thu, 18 Dec 2014 11:25:08 +0000 http://www.androidhive.info/?p=26429 My previous tutorial explains how to download a file by showing a progress bar. In this article I am going to explain how to upload a file to server by showing the progress bar. Using this tutorial you can build an app like Instagram where you can capture image or record a video using camera and then upload to a server. On the server side, I used PHP language to read the file and moved it to a particular location.

The best thing about this article is, it works well with larger file uploads too without any out of memory errors. I have tested the app by uploading 50MB file flawlessly.

android file upload with progress bar


Prerequisite

As this article uploads the image/video taken from camera, you need to have knowledge over android camera module. So I recommend you go through my previous tutorial Android Working with Camera which gives you an overview of integrating camera in your android apps.


1. Creating Android Project

1. In Eclipse create a new android project by navigating to File ⇒ New ⇒ Android Application Project and fill out all the required details.

2. Open strings.xml located under res ⇒ values and add below string values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Camera File Upload</string>
    <string name="btnTakePicture">Capture Image</string>
    <string name="btnRecordVideo">Record Video</string>
    <string name="or">(or)</string>
    <string name="btnUploadToServer">Upload to Server</string>

</resources>



3. Add below color values in colors.xml located under res ⇒ values folder.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="view_background">#e8ecfa</color>
    <color name="btn_bg">#277bec</color>
    <color name="white">#ffffff</color>
    <color name="txt_font">#4e5572</color>
    <color name="action_bar">#1f2649</color>

</resources>



4. Now under src folder create a new class named Config.java. This class file contains file upload URL and image directory name to save the image/video on mobile memory. You will have to replace the file upload url with yours while testing.

package info.androidhive.camerafileupload;

public class Config {
	// File upload url (replace the ip with your server address)
	public static final String FILE_UPLOAD_URL = "http://192.168.0.104/AndroidFileUpload/fileUpload.php";
	
	// Directory name to store captured images and videos
    public static final String IMAGE_DIRECTORY_NAME = "Android File Upload";
}



5. Create a class named AndroidMultiPartEntity.java and paste below code. This class is a custom MultipartEntity class which provides very important functionality required for this project such as progress bar incrementation.

package info.androidhive.camerafileupload;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

@SuppressWarnings("deprecation")
public class AndroidMultiPartEntity extends MultipartEntity

{

	private final ProgressListener listener;

	public AndroidMultiPartEntity(final ProgressListener listener) {
		super();
		this.listener = listener;
	}

	public AndroidMultiPartEntity(final HttpMultipartMode mode,
			final ProgressListener listener) {
		super(mode);
		this.listener = listener;
	}

	public AndroidMultiPartEntity(HttpMultipartMode mode, final String boundary,
			final Charset charset, final ProgressListener listener) {
		super(mode, boundary, charset);
		this.listener = listener;
	}

	@Override
	public void writeTo(final OutputStream outstream) throws IOException {
		super.writeTo(new CountingOutputStream(outstream, this.listener));
	}

	public static interface ProgressListener {
		void transferred(long num);
	}

	public static class CountingOutputStream extends FilterOutputStream {

		private final ProgressListener listener;
		private long transferred;

		public CountingOutputStream(final OutputStream out,
				final ProgressListener listener) {
			super(out);
			this.listener = listener;
			this.transferred = 0;
		}

		public void write(byte[] b, int off, int len) throws IOException {
			out.write(b, off, len);
			this.transferred += len;
			this.listener.transferred(this.transferred);
		}

		public void write(int b) throws IOException {
			out.write(b);
			this.transferred++;
			this.listener.transferred(this.transferred);
		}
	}
}



Now we’ll add camera support in our app by creating a simple screen with two buttons to invoke camera app to capture image or record video.

6. Open your AndroidManifest.xml file and add required permissions. You can notice that UploadActivity also added in below manifest file. We’ll create it in few minutes.

INTERNET – Required to make network calls
WRITE_EXTERNAL_STORAGE – Required to store image/video on to storage
RECORD_AUDIO – Required to record audio along with video

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

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="info.androidhive.camerafileupload.UploadActivity"
            android:screenOrientation="portrait" >
        </activity>
    </application>

</manifest>



7. Open the layout file of your main activity (activity_main.xml) and add below code. This creates a layout with two buttons.

<?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="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/view_background"
    android:baselineAligned="false"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical" >

        <!-- Capture picture button -->

        <Button
            android:id="@+id/btnCapturePicture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnTakePicture"
            android:textColor="@color/white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:gravity="center_horizontal"
            android:text="@string/or"
            android:textColor="@color/txt_font" />

        <!-- Record video button -->

        <Button
            android:id="@+id/btnRecordVideo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnRecordVideo"
            android:textColor="@color/white" />
    </LinearLayout>

</RelativeLayout>



8. Add below camera related code in your MainActivity.java class. This code is directly taken from this tutorial.

In brief what this activity will do is,

> Camera app will be launched on tapping take picture or record video button.
> Once the image / video is captured, it will be stored on to mobile SDCard.
> Finally UploadActivity will be launched by passing the SDCard path of the media that is captured. The process of uploading will be done in UploadActivity.

package info.androidhive.camerafileupload;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();
	
 
    // Camera activity request codes
    private static final int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100;
    private static final int CAMERA_CAPTURE_VIDEO_REQUEST_CODE = 200;
    
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
 
    private Uri fileUri; // file url to store image/video
    
    private Button btnCapturePicture, btnRecordVideo;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Changing action bar background color
        // These two lines are not needed
        getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor(getResources().getString(R.color.action_bar))));
 
        btnCapturePicture = (Button) findViewById(R.id.btnCapturePicture);
        btnRecordVideo = (Button) findViewById(R.id.btnRecordVideo);
 
        /**
         * Capture image button click event
         */
        btnCapturePicture.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // capture picture
                captureImage();
            }
        });
 
        /**
         * Record video button click event
         */
        btnRecordVideo.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // record video
                recordVideo();
            }
        });
 
        // Checking camera availability
        if (!isDeviceSupportCamera()) {
            Toast.makeText(getApplicationContext(),
                    "Sorry! Your device doesn't support camera",
                    Toast.LENGTH_LONG).show();
            // will close the app if the device does't have camera
            finish();
        }
    }
 
    /**
     * Checking device has camera hardware or not
     * */
    private boolean isDeviceSupportCamera() {
        if (getApplicationContext().getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_CAMERA)) {
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }
 
    /**
     * Launching camera app to capture image
     */
    private void captureImage() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
 
        // start the image capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE);
    }
    
    /**
     * Launching camera app to record video
     */
    private void recordVideo() {
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
 
        // set video quality
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file
                                                            // name
 
        // start the video capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);
    }
 
    /**
     * Here we store the file url as it will be null after returning from camera
     * app
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
 
        // save file url in bundle as it will be null on screen orientation
        // changes
        outState.putParcelable("file_uri", fileUri);
    }
 
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
 
        // get the file url
        fileUri = savedInstanceState.getParcelable("file_uri");
    }
 
    
 
    /**
     * Receiving activity result method will be called after closing the camera
     * */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // if the result is capturing Image
        if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// successfully captured the image
                // launching upload activity
            	launchUploadActivity(true);
            	
            	
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled Image capture
                Toast.makeText(getApplicationContext(),
                        "User cancelled image capture", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to capture image
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to capture image", Toast.LENGTH_SHORT)
                        .show();
            }
        
        } else if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// video successfully recorded
                // launching upload activity
            	launchUploadActivity(false);
            
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled recording
                Toast.makeText(getApplicationContext(),
                        "User cancelled video recording", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to record video
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to record video", Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }
    
    private void launchUploadActivity(boolean isImage){
    	Intent i = new Intent(MainActivity.this, UploadActivity.class);
        i.putExtra("filePath", fileUri.getPath());
        i.putExtra("isImage", isImage);
        startActivity(i);
    }
     
    /**
     * ------------ Helper Methods ---------------------- 
     * */
 
    /**
     * Creating file uri to store image/video
     */
    public Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }
 
    /**
     * returning image / video
     */
    private static File getOutputMediaFile(int type) {
 
        // External sdcard location
        File mediaStorageDir = new File(
                Environment
                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                Config.IMAGE_DIRECTORY_NAME);
 
        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d(TAG, "Oops! Failed create "
                        + Config.IMAGE_DIRECTORY_NAME + " directory");
                return null;
            }
        }
 
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                Locale.getDefault()).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "VID_" + timeStamp + ".mp4");
        } else {
            return null;
        }
 
        return mediaFile;
    }
}



Now if you run the app, you should see following output.

android-file-upload-camera-screen
android-file-upload-camera-taking-camera-picture



Once you are able to launch camera and capture images, we can move forward and start creating the upload activity.

9. Create an xml file under res ⇒ layout folder named activity_upload.xml. This layout contains ImageView, VideoView to preview the captured media and a ProgressBar to show uploading progress.

<?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:background="@color/view_background"
    android:orientation="vertical"
    android:padding="10dp" >

    

    <!-- To display picture taken -->

    <ImageView
        android:id="@+id/imgPreview"
        android:layout_width="fill_parent"
        android:layout_height="200dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <!-- Videoview to preview recorded video -->

    <VideoView
        android:id="@+id/videoPreview"
        android:layout_width="fill_parent"
        android:layout_height="400dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <TextView
        android:id="@+id/txtPercentage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="15dp"
        android:layout_marginTop="15dp"
        android:textColor="@color/txt_font"
        android:textSize="30dp" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="20dp"
        android:layout_marginBottom="35dp" 
        android:visibility="gone"/>

    <Button
        android:id="@+id/btnUpload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:background="@color/btn_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="@string/btnUploadToServer"
        android:textColor="@color/white" 
        android:layout_marginBottom="20dp"/>

</LinearLayout>



10. Create a class named UploadActivity.java and paste below code. In this activity

> The path of captured camera image/video is received from MainActivity and image/video is displayed on the screen for preview purpose.
> UploadFileToServer async method takes care of uploading file to server and updating the Progress Bar.

package info.androidhive.camerafileupload;

import info.androidhive.camerafileupload.AndroidMultiPartEntity.ProgressListener;

import java.io.File;
import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

public class UploadActivity extends Activity {
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private ProgressBar progressBar;
	private String filePath = null;
	private TextView txtPercentage;
	private ImageView imgPreview;
	private VideoView vidPreview;
	private Button btnUpload;
	long totalSize = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_upload);
		txtPercentage = (TextView) findViewById(R.id.txtPercentage);
		btnUpload = (Button) findViewById(R.id.btnUpload);
		progressBar = (ProgressBar) findViewById(R.id.progressBar);
		imgPreview = (ImageView) findViewById(R.id.imgPreview);
		vidPreview = (VideoView) findViewById(R.id.videoPreview);

		// Changing action bar background color
		getActionBar().setBackgroundDrawable(
				new ColorDrawable(Color.parseColor(getResources().getString(
						R.color.action_bar))));

		// Receiving the data from previous activity
		Intent i = getIntent();

		// image or video path that is captured in previous activity
		filePath = i.getStringExtra("filePath");

		// boolean flag to identify the media type, image or video
		boolean isImage = i.getBooleanExtra("isImage", true);

		if (filePath != null) {
			// Displaying the image or video on the screen
			previewMedia(isImage);
		} else {
			Toast.makeText(getApplicationContext(),
					"Sorry, file path is missing!", Toast.LENGTH_LONG).show();
		}

		btnUpload.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// uploading the file to server
				new UploadFileToServer().execute();
			}
		});

	}

	/**
	 * Displaying captured image/video on the screen
	 * */
	private void previewMedia(boolean isImage) {
		// Checking whether captured media is image or video
		if (isImage) {
			imgPreview.setVisibility(View.VISIBLE);
			vidPreview.setVisibility(View.GONE);
			// bimatp factory
			BitmapFactory.Options options = new BitmapFactory.Options();

			// down sizing image as it throws OutOfMemory Exception for larger
			// images
			options.inSampleSize = 8;

			final Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

			imgPreview.setImageBitmap(bitmap);
		} else {
			imgPreview.setVisibility(View.GONE);
			vidPreview.setVisibility(View.VISIBLE);
			vidPreview.setVideoPath(filePath);
			// start playing
			vidPreview.start();
		}
	}

	/**
	 * Uploading the file to server
	 * */
	private class UploadFileToServer extends AsyncTask<Void, Integer, String> {
		@Override
		protected void onPreExecute() {
			// setting progress bar to zero
			progressBar.setProgress(0);
			super.onPreExecute();
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			// Making progress bar visible
			progressBar.setVisibility(View.VISIBLE);

			// updating progress bar value
			progressBar.setProgress(progress[0]);

			// updating percentage value
			txtPercentage.setText(String.valueOf(progress[0]) + "%");
		}

		@Override
		protected String doInBackground(Void... params) {
			return uploadFile();
		}

		@SuppressWarnings("deprecation")
		private String uploadFile() {
			String responseString = null;

			HttpClient httpclient = new DefaultHttpClient();
			HttpPost httppost = new HttpPost(Config.FILE_UPLOAD_URL);

			try {
				AndroidMultiPartEntity entity = new AndroidMultiPartEntity(
						new ProgressListener() {

							@Override
							public void transferred(long num) {
								publishProgress((int) ((num / (float) totalSize) * 100));
							}
						});

				File sourceFile = new File(filePath);

				// Adding file data to http body
				entity.addPart("image", new FileBody(sourceFile));

				// Extra parameters if you want to pass to server
				entity.addPart("website",
						new StringBody("www.androidhive.info"));
				entity.addPart("email", new StringBody("abc@gmail.com"));

				totalSize = entity.getContentLength();
				httppost.setEntity(entity);

				// Making server call
				HttpResponse response = httpclient.execute(httppost);
				HttpEntity r_entity = response.getEntity();

				int statusCode = response.getStatusLine().getStatusCode();
				if (statusCode == 200) {
					// Server response
					responseString = EntityUtils.toString(r_entity);
				} else {
					responseString = "Error occurred! Http Status Code: "
							+ statusCode;
				}

			} catch (ClientProtocolException e) {
				responseString = e.toString();
			} catch (IOException e) {
				responseString = e.toString();
			}

			return responseString;

		}

		@Override
		protected void onPostExecute(String result) {
			Log.e(TAG, "Response from server: " + result);

			// showing the server response in an alert dialog
			showAlert(result);

			super.onPostExecute(result);
		}

	}

	/**
	 * Method to show alert dialog
	 * */
	private void showAlert(String message) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setMessage(message).setTitle("Response from Servers")
				.setCancelable(false)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						// do nothing
					}
				});
		AlertDialog alert = builder.create();
		alert.show();
	}

}



Until now we are done with android project. Now let’s quickly create the PHP project to receive the file that is being sent from android app. But before that, we need to do small configuration changes to WAMP server.

2. Installing & Configuring WAMP Server

1. Download and install WAMP software. On windows machine, WAMP will be installed at C:\wamp location.

2. Open php.ini and modify below values. By default wamp server allows maximum of 2MB file only to upload. After changing the below values, you can upload the files upto 50MB size.

wamp-server-editing-php.ini-file
upload_max_filesize = 50M
post_max_size = 50M
max_input_time = 300
max_execution_time = 300

3. Now restart the WAMP server.


3. Creating PHP Project

1. Go inside C:\wamp\www and create a folder named AndroidFileUpload. This will be the root directory of our project.

2. Now go into AndroidFileUpload folder and create a folder named uploads to keep all the uploaded files.

3. Create a file named fileUpload.php and paste below content. Below php code takes care of receiving the files from android app and store them in uploads folder. Upon the processing the file, server responds with a JSON message.

<?php

// Path to move uploaded files
$target_path = "uploads/";

// array for final json respone
$response = array();

// getting server ip address
$server_ip = gethostbyname(gethostname());

// final file url that is being uploaded
$file_upload_url = 'http://' . $server_ip . '/' . 'AndroidFileUpload' . '/' . $target_path;


if (isset($_FILES['image']['name'])) {
    $target_path = $target_path . basename($_FILES['image']['name']);

    // reading other post parameters
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $website = isset($_POST['website']) ? $_POST['website'] : '';

    $response['file_name'] = basename($_FILES['image']['name']);
    $response['email'] = $email;
    $response['website'] = $website;

    try {
        // Throws exception incase file is not being moved
        if (!move_uploaded_file($_FILES['image']['tmp_name'], $target_path)) {
            // make error flag true
            $response['error'] = true;
            $response['message'] = 'Could not move the file!';
        }

        // File successfully uploaded
        $response['message'] = 'File uploaded successfully!';
        $response['error'] = false;
        $response['file_path'] = $file_upload_url . basename($_FILES['image']['name']);
    } catch (Exception $e) {
        // Exception occurred. Make error flag true
        $response['error'] = true;
        $response['message'] = $e->getMessage();
    }
} else {
    // File parameter is missing
    $response['error'] = true;
    $response['message'] = 'Not received any file!F';
}

// Echo final json response to client
echo json_encode($response);
?>



Below is the sample JSON response if the file is uploaded successfully. You can use error value to verify the upload on android side.

{
    "file_name": "DSC_0021.JPG",
    "email": "admin@androidhive.info",
    "website": "www.androidhive.info",
    "message": "File uploaded successfully!",
    "error": false,
    "file_path": "http://192.168.0.104/AndroidFileUpload/uploads/DSC_0021.JPG"
}


4. Testing the File Upload (localhost)

The following steps shows you how to test the both apps together locally.

1. Connect the both the devices (machine running the wamp server & android mobile) to same wifi network.

2. Start the WAMP server.

3. Get the ip address of the machine that is running the PHP project. You can get the ip address by typing ipconfig in command prompt. (On mac os, use ifconfig to get the ip address)

4. Replace the ip address in Config.java (check 4th step in android project) with your ip address.

5. Deploy & run the android app on the mobile.

android-uploading-camera-picture-to-server
android-uploading-camera-picture-to-server1
android-uploading-camera-picture-to-server2



References
1. Stackoverflow Question about file upload with progress bar.

2. Icon that I used as app icon.

]]>
http://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/feed/ 0
Google Glass CardScrollView Examplehttp://www.androidhive.info/2014/12/google-glass-cardscrollview-example/ http://www.androidhive.info/2014/12/google-glass-cardscrollview-example/#comments Tue, 09 Dec 2014 04:16:10 +0000 http://www.androidhive.info/?p=26239 Google Glass CardScrollView is similar to ListView that we see in android mobile. On glass you can consider each list item as a Card Item which occupies fullscreen of the glass. CardScrollView allows us to swipe through the cards either from left to right, or vice versa.

This tutorial explains how to implement CardScrollView with a custom adapter class.

google glass card scroll view adapter



google glass card scroll view adapter



Creating New Glass Project

1. In Eclipse go to File ⇒ New ⇒ Android Application Project and give application name, project name and package.

2. Set Minimum Required SDK and Target SDK to API 19: Android 4.4 (KitKat), Compile With to Glass Development Kit Sneak Peek (Google Inc.) (API19) and select the Theme to None

3. Once the project is created, open AndroidManifest.xml file and remove the theme android:theme property to allow glass to apply it’s own theme.

4. Open strings.xml and add below string values.

<!--?xml version="1.0" encoding="utf-8"?-->
<resources>

    <string name="app_name">Card ScrollView</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    
    <!-- "ok glass" voice command -->
    <string name="start_command">Movie Cards</string>

</resources>



5. Download this drawable.zip and paste the contents in project’s res ⇒ drawable folder. This downloaded folder contains card images required for this project.

6. Create two packages named model and adapter in your project.

7. Under model package, create a new class named MovieCard.java and paste below code. This model class represents single card item in CardScrollView.

package info.androidhive.cardscrollview.model;

import com.google.android.glass.app.Card.ImageLayout;

public class MovieCard {

	private String text;
	private String footerText;
	private ImageLayout imgLayout;
	private int[] images;

	public MovieCard() {
	}

	public MovieCard(String text, String footerText,
			ImageLayout imgLayout, int[] images) {		
		this.text = text;
		this.footerText = footerText;
		this.imgLayout = imgLayout;
		this.images = images;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public String getFooterText() {
		return footerText;
	}

	public void setFooterText(String footerText) {
		this.footerText = footerText;
	}

	public ImageLayout getImgLayout() {
		return imgLayout;
	}

	public void setImgLayout(ImageLayout imgLayout) {
		this.imgLayout = imgLayout;
	}

	public int[] getImages() {
		return images;
	}

	public void setImages(int[] images) {
		this.images = images;
	}

}


8. Now under adapter package, create a class named MovieCardsAdapter.java. This is the custom adapter class which provides data to cardscrollview.

package info.androidhive.cardscrollview.adapter;

import info.androidhive.cardscrollview.model.MovieCard;

import java.util.List;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.glass.app.Card;
import com.google.android.glass.widget.CardScrollAdapter;

public class MovieCardsAdapter extends CardScrollAdapter {
	private List<moviecard> mCards;
	private Context context;

	public MovieCardsAdapter(Context context, List<moviecard> mCards) {
		this.context = context;
		this.mCards = mCards;
	}

	@Override
	public int getPosition(Object item) {
		return mCards.indexOf(item);
	}

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

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Card card = new Card(context);

		MovieCard mc = mCards.get(position);
		
		// Card text
		if (mc.getText() != null)
			card.setText(mc.getText());

		// Card footer note
		if (mc.getFooterText() != null)
			card.setFootnote(mc.getFooterText());

		// Set image layout
		if (mc.getImgLayout() != null)
			card.setImageLayout(mc.getImgLayout());
		
		// loop and set card images
		for(int img : mc.getImages()){
			card.addImage(img);
		}

		return card.getView();
	}

	

}



9. Open your main activity class MainActivity.java and do the below changes. In this activity we are adding the card items in prepareMovieCards() method.

package info.androidhive.cardscrollview;

import info.androidhive.cardscrollview.R;
import info.androidhive.cardscrollview.adapter.MovieCardsAdapter;
import info.androidhive.cardscrollview.model.MovieCard;

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

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;

import com.google.android.glass.app.Card.ImageLayout;
import com.google.android.glass.widget.CardScrollView;

public class MainActivity extends Activity {

	private List<moviecard> mCards;
	private CardScrollView mCardScrollView;
	private Context context;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		context = this;

		prepareMovieCards();

		mCardScrollView = new CardScrollView(this);
		MovieCardsAdapter adapter = new MovieCardsAdapter(context, mCards);
		mCardScrollView.setAdapter(adapter);
		mCardScrollView.activate();
		setContentView(mCardScrollView);
	}

	private void prepareMovieCards() {
		mCards = new ArrayList<moviecard>();

		// Card with no background image
		MovieCard mc = new MovieCard("I don't know. But who cares! Ha ha!",
				"Wait! What does that mean?", ImageLayout.FULL, new int[] {});
		mCards.add(mc);

		// Card with full background image
		mc = new MovieCard("I wanna go home. Does anyone know where my dad is?",
				"Pet store?", ImageLayout.FULL,
				new int[] { R.drawable.card_full });
		mCards.add(mc);

		// Card with full background of 3 images
		mc = new MovieCard("Dude? Dude? Focus dude... Dude?",
				"Oh, he lives. Hey, dude!", ImageLayout.FULL, new int[] {
						R.drawable.card_bottom_left,
						R.drawable.card_bottom_right, R.drawable.card_top });
		mCards.add(mc);

		// Card with left aligned images
		mc = new MovieCard("Just keep swimming.",
				"I'm sorry, Dory. But I... do", ImageLayout.LEFT, new int[] {
						R.drawable.card_bottom_left,
						R.drawable.card_bottom_right, R.drawable.card_top });
		mCards.add(mc);

	}
}



10. Before the running the app, add the app to Ok Glass menu by doing following changes. Create a folder named xml under res folder.

11. Under xml folder, create an xml file named voice_trigger_start.xml and add below content.

<!--?xml version="1.0" encoding="utf-8"?-->
<trigger keyword="@string/start_command">



12. Finally open the AndroidManifest.xml and add the voice related actions to launch the app by voice command.

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

    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    
    <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.cardscrollview.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>

            <meta-data
                android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/voice_trigger_start" />
        </activity>
    </application>

</manifest>

Now run the app and say “Ok Glass Movie Cards” to launch the app. Once the app is launched, you can see the CardScrollView with beautiful images as shown in the above top demo.

]]>
http://www.androidhive.info/2014/12/google-glass-cardscrollview-example/feed/ 0
Android Building Group Chat App using Sockets – Part 2http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/ http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/#comments Thu, 30 Oct 2014 14:40:12 +0000 http://www.androidhive.info/?p=25668 In the 1st part, we have learned how to build the socket server and the web chat app. We also did tested the socket server using the web app.

In this part we are going to build the next important component, i.e. android chat app. The app we are about to create will have two screens. The first screen will prompt the user to enter his/her name. This name is to identify the sender whenever a message is received. The second screen is to list the chat messages and to compose a new message.

So let’s start the app by creating a new android project in Eclipse IDE.

android building chat app using java sockets


6. Building The Android Chat App

1. In Eclipse create new android project by navigating to File ⇒ New ⇒ Android Application Project and fill out all the required details.

I gave my project name as WebMobileGroupChat and package name as
info.androidhive.webgroupchat.

2. Add the below color values in res ⇒ values ⇒ colors.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="actionbar">#3cb879</color>
    <color name="body_background">#e8e8e8</color>
    <color name="body_background_green">#82e783</color>
    <color name="server_status_bar">#2b2b2b</color>
    <color name="title_gray">#434343</color>
    <color name="white">#ffffff</color>
    <color name="bg_msg_you">#5eb964</color>
    <color name="bg_msg_from">#e5e7eb</color>
    <color name="msg_border_color">#a1a1a1</color>
    <color name="bg_btn_join">#1e6258</color>
    <color name="bg_msg_input">#e8e8e8</color>
    <color name="text_msg_input">#626262</color>
    <color name="lblFromName">#777777</color>
</resources>



3. Also add the below string values in res ⇒ values ⇒ strings.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">WebMobileGroupChat</string>
    <string name="title">(Android WebSockets Chat App)</string>
    <string name="author_name">By Ravi Tamada</string>
    <string name="author_url">www.androidhive.info</string>
    <string name="enter_name">Enter your name</string>
    <string name="btn_join">JOIN</string>
    <string name="btn_send">Send</string>

</resources>



4. Edit styles.xml located under res ⇒ values ⇒ styles.xml and add below styles. Here we are adding the styles for the action bar.

<resources>
 
    <style name="ChatAppTheme" parent="@android:style/Theme.Holo.Light">
        <item name="android:actionBarStyle">@style/MyActionBarTheme</item>
    </style>
 
    <style name="MyActionBarTheme" parent="@android:style/Widget.Holo.Light.ActionBar">
        <item name="android:background">@color/actionbar</item>
        <item name="android:titleTextStyle">@style/TitleTextStyle</item>
    </style>
    
     <style name="TitleTextStyle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
        <item name="android:textColor">@color/white</item>
    </style>
 
</resources>



5. Now we need an activity to take the username that is required when connecting to socket server. So under res ⇒ layout folder create an xml file named activity_name.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/actionbar"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/imgLogo"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="60dp"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/imgLogo"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/title"
        android:textColor="@color/white"
        android:textSize="13dp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="20dp" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="15dp"
            android:text="@string/enter_name"
            android:textColor="@color/white"
            android:textSize="18dp" />

        <EditText
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="10dp"
            android:background="@color/white"
            android:inputType="textCapWords"
            android:padding="10dp" />

        <Button
            android:id="@+id/btnJoin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:background="@color/bg_btn_join"
            android:paddingLeft="25dp"
            android:paddingRight="25dp"
            android:text="@string/btn_join"
            android:textColor="@color/white" />
    </LinearLayout>

    <!-- author info -->

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/author_name"
            android:textColor="@color/white"
            android:textSize="12dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/author_url"
            android:textColor="@color/white"
            android:textSize="12dp" />
    </LinearLayout>

</RelativeLayout>



6. Create a new activity named NameActivity.java under project’s main package. In this activity we don’t handle anything complex. We just take the user input from EditText and send it to other activity.

package info.androidhive.webgroupchat;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class NameActivity extends Activity {

	private Button btnJoin;
	private EditText txtName;

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

		btnJoin = (Button) findViewById(R.id.btnJoin);
		txtName = (EditText) findViewById(R.id.name);

		// Hiding the action bar
		getActionBar().hide();

		btnJoin.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				if (txtName.getText().toString().trim().length() > 0) {

					String name = txtName.getText().toString().trim();

					Intent intent = new Intent(NameActivity.this,
							MainActivity.class);
					intent.putExtra("name", name);

					startActivity(intent);

				} else {
					Toast.makeText(getApplicationContext(),
							"Please enter your name", Toast.LENGTH_LONG).show();
				}
			}
		});
	}
}



7. Finally make NameActivity.java as launcher activity in AndroidManifest.xml. Also add INTERNET permission as we need to make internet calls. This is how your manifest file should look like.

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

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

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

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

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

</manifest>



After doing the above changes, if you run the app, you should see the name activity launched as first activity. Below is the output of name activity where user can enter their name and move to next activity.

android building chat app using java sockets

Before going to implement sockets, I would like to create few resource files first which required to create messages interface.

8. Download this background image and paste it in project’s res ⇒ drawable folder. (If you don’t see drawable folder, create a new one and name it as drawable). This image will be used as background repeat image for the chat conversation.

9. Create 3 new xml files under drawable folder named tile_bg.xml, bg_msg_from.xml and bg_msg_you.xml and add below codes. These drawable xml files are used as background for chat messages.

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
  android:src="@drawable/bg_messages" 
  android:tileMode="repeat" />
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/bg_msg_from" >
    </solid>

    <corners android:radius="5dp" >
    </corners>

</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/bg_msg_you" >
    </solid>

    <corners android:radius="5dp" >
    </corners>

</shape>



10. Now under res ⇒ layout folder create two more xml files named list_item_message_left.xml and list_item_message_right.xml. These two layout files are used to align chat messages on left and right in the list view.

<?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"
    android:paddingBottom="5dp"
    android:paddingTop="5dp" 
    android:paddingLeft="10dp">

    <TextView
        android:id="@+id/lblMsgFrom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:textColor="@color/lblFromName"
        android:textStyle="italic" 
        android:padding="5dp"/>

    <TextView
        android:id="@+id/txtMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:layout_marginRight="80dp"
        android:textColor="@color/title_gray"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="@drawable/bg_msg_from"/>

</LinearLayout>
<?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:gravity="right"
    android:orientation="vertical"
    android:paddingBottom="5dp"
    android:paddingRight="10dp"
    android:paddingTop="5dp" >

    <TextView
        android:id="@+id/lblMsgFrom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textColor="@color/lblFromName"
        android:textSize="12dp"
        android:textStyle="italic" />

    <TextView
        android:id="@+id/txtMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="80dp"
        android:background="@drawable/bg_msg_you"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:textColor="@color/white"
        android:textSize="16dp" />

</LinearLayout>



11. Now we need to create another layout to list all the chat messages and an option to compose a new message. Create another layout activity_main.xml and add below code.

<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:background="@drawable/tile_bg"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/list_view_messages"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@null"
        android:divider="@null"
        android:transcriptMode="alwaysScroll" 
        android:stackFromBottom="true">
    </ListView>

    <LinearLayout
        android:id="@+id/llMsgCompose"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="horizontal"
        android:weightSum="3" >

        <EditText
            android:id="@+id/inputMsg"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="2"
            android:background="@color/bg_msg_input"
            android:textColor="@color/text_msg_input"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"/>

        <Button
            android:id="@+id/btnSend"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@color/bg_btn_join"
            android:textColor="@color/white" 
            android:text="@string/btn_send" />
    </LinearLayout>

</LinearLayout>



12. With the above step, the creation of layout resources is done. Now we’ll quickly create few helper classes. In your project create a new package and name it as other.

After creating the new package my package name will be info.androidhive.webgroupchat.other.

13. In other package, create a class named Utils.java and add below code. This class contains methods to save the user’s session id in shared preferences.

package info.androidhive.webgroupchat.other;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

public class Utils {

	private Context context;
	private SharedPreferences sharedPref;

	private static final String KEY_SHARED_PREF = "ANDROID_WEB_CHAT";
	private static final int KEY_MODE_PRIVATE = 0;
	private static final String KEY_SESSION_ID = "sessionId",
			FLAG_MESSAGE = "message";

	public Utils(Context context) {
		this.context = context;
		sharedPref = this.context.getSharedPreferences(KEY_SHARED_PREF,
				KEY_MODE_PRIVATE);
	}

	public void storeSessionId(String sessionId) {
		Editor editor = sharedPref.edit();
		editor.putString(KEY_SESSION_ID, sessionId);
		editor.commit();
	}

	public String getSessionId() {
		return sharedPref.getString(KEY_SESSION_ID, null);
	}

	public String getSendMessageJSON(String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_MESSAGE);
			jObj.put("sessionId", getSessionId());
			jObj.put("message", message);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

}



14. Create another class named Message.java. This model class defines each chat message where it contains message id, text and a boolean flag (isSelf) to define message owner. Using this boolean flag we’ll align message left or right in the list view.

package info.androidhive.webgroupchat.other;

public class Message {
	private String fromName, message;
	private boolean isSelf;

	public Message() {
	}

	public Message(String fromName, String message, boolean isSelf) {
		this.fromName = fromName;
		this.message = message;
		this.isSelf = isSelf;
	}

	public String getFromName() {
		return fromName;
	}

	public void setFromName(String fromName) {
		this.fromName = fromName;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public boolean isSelf() {
		return isSelf;
	}

	public void setSelf(boolean isSelf) {
		this.isSelf = isSelf;
	}

}



15. Create a class named WsConfig.java. This is where we define socket configuration i.e the socket url, port number and end point.

package info.androidhive.webgroupchat.other;

public class WsConfig {
	public static final String URL_WEBSOCKET = "ws://192.168.0.102:8080/WebMobileGroupChatServer/chat?name=";
}



16. Now under your main package create a class named MessagesListAdapter.java to implement the custom list view adapter class. This class plays a major role in rendering the list by aligning the chat messages left or right.

package info.androidhive.webgroupchat;

import info.androidhive.webgroupchat.other.Message;

import java.util.List;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class MessagesListAdapter extends BaseAdapter {

	private Context context;
	private List<Message> messagesItems;

	public MessagesListAdapter(Context context, List<Message> navDrawerItems) {
		this.context = context;
		this.messagesItems = navDrawerItems;
	}

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

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

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

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

		/**
		 * The following list not implemented reusable list items as list items
		 * are showing incorrect data Add the solution if you have one
		 * */

		Message m = messagesItems.get(position);

		LayoutInflater mInflater = (LayoutInflater) context
				.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

		// Identifying the message owner
		if (messagesItems.get(position).isSelf()) {
			// message belongs to you, so load the right aligned layout
			convertView = mInflater.inflate(R.layout.list_item_message_right,
					null);
		} else {
			// message belongs to other person, load the left aligned layout
			convertView = mInflater.inflate(R.layout.list_item_message_left,
					null);
		}

		TextView lblFrom = (TextView) convertView.findViewById(R.id.lblMsgFrom);
		TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);

		txtMsg.setText(m.getMessage());
		lblFrom.setText(m.getFromName());

		return convertView;
	}
}



17. Download the android websockets library and extract somewhere. Thanks to Koush for writing such a useful library.

18. Import the downloaded android websockets library into Eclipse workspace. Goto File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace and select the downloaded library project home directory.

19. Now add this project as a Library to our project. Right Click on project ⇒ Properties ⇒ Android (on left) ⇒ Add (on right, under Library section) and select the imported project.

android-adding-library-project

20. Finally open the main activity class (MainActivity.java) do the below changes. The below code very simple and everything is self explanatory.

> A web socket is created using WebSocketClient class and it has all the callback methods like onConnect, onMessage and onDisconnect.

> In onMessage method parseMessage() is called to parse the JSON received from the socket server.

> In parseMessage() method, the purpose of JSON is identified by reading the flag value.

> When a new message is received, the message is added to list view data source and adapter.notifyDataSetChanged() is called to update the chat list.

> sendMessageToServer() method is used to send the message from android device to socket server.

> playBeep() method is called to play device’s default notification sound whenever a new message is received.

package info.androidhive.webgroupchat;

import info.androidhive.webgroupchat.other.Message;
import info.androidhive.webgroupchat.other.Utils;
import info.androidhive.webgroupchat.other.WsConfig;

import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import com.codebutler.android_websockets.WebSocketClient;

public class MainActivity extends Activity {

	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private Button btnSend;
	private EditText inputMsg;

	private WebSocketClient client;

	// Chat messages list adapter
	private MessagesListAdapter adapter;
	private List<Message> listMessages;
	private ListView listViewMessages;

	private Utils utils;

	// Client name
	private String name = null;

	// JSON flags to identify the kind of JSON response
	private static final String TAG_SELF = "self", TAG_NEW = "new",
			TAG_MESSAGE = "message", TAG_EXIT = "exit";

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

		btnSend = (Button) findViewById(R.id.btnSend);
		inputMsg = (EditText) findViewById(R.id.inputMsg);
		listViewMessages = (ListView) findViewById(R.id.list_view_messages);

		utils = new Utils(getApplicationContext());

		// Getting the person name from previous screen
		Intent i = getIntent();
		name = i.getStringExtra("name");

		btnSend.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// Sending message to web socket server
				sendMessageToServer(utils.getSendMessageJSON(inputMsg.getText()
						.toString()));

				// Clearing the input filed once message was sent
				inputMsg.setText("");
			}
		});

		listMessages = new ArrayList<Message>();

		adapter = new MessagesListAdapter(this, listMessages);
		listViewMessages.setAdapter(adapter);

		/**
		 * Creating web socket client. This will have callback methods
		 * */
		client = new WebSocketClient(URI.create(WsConfig.URL_WEBSOCKET
				+ URLEncoder.encode(name)), new WebSocketClient.Listener() {
			@Override
			public void onConnect() {

			}

			/**
			 * On receiving the message from web socket server
			 * */
			@Override
			public void onMessage(String message) {
				Log.d(TAG, String.format("Got string message! %s", message));

				parseMessage(message);

			}

			@Override
			public void onMessage(byte[] data) {
				Log.d(TAG, String.format("Got binary message! %s",
						bytesToHex(data)));

				// Message will be in JSON format
				parseMessage(bytesToHex(data));
			}

			/**
			 * Called when the connection is terminated
			 * */
			@Override
			public void onDisconnect(int code, String reason) {

				String message = String.format(Locale.US,
						"Disconnected! Code: %d Reason: %s", code, reason);

				showToast(message);

				// clear the session id from shared preferences
				utils.storeSessionId(null);
			}

			@Override
			public void onError(Exception error) {
				Log.e(TAG, "Error! : " + error);

				showToast("Error! : " + error);
			}

		}, null);

		client.connect();
	}

	/**
	 * Method to send message to web socket server
	 * */
	private void sendMessageToServer(String message) {
		if (client != null && client.isConnected()) {
			client.send(message);
		}
	}

	/**
	 * Parsing the JSON message received from server The intent of message will
	 * be identified by JSON node 'flag'. flag = self, message belongs to the
	 * person. flag = new, a new person joined the conversation. flag = message,
	 * a new message received from server. flag = exit, somebody left the
	 * conversation.
	 * */
	private void parseMessage(final String msg) {

		try {
			JSONObject jObj = new JSONObject(msg);

			// JSON node 'flag'
			String flag = jObj.getString("flag");

			// if flag is 'self', this JSON contains session id
			if (flag.equalsIgnoreCase(TAG_SELF)) {

				String sessionId = jObj.getString("sessionId");

				// Save the session id in shared preferences
				utils.storeSessionId(sessionId);

				Log.e(TAG, "Your session id: " + utils.getSessionId());

			} else if (flag.equalsIgnoreCase(TAG_NEW)) {
				// If the flag is 'new', new person joined the room
				String name = jObj.getString("name");
				String message = jObj.getString("message");

				// number of people online
				String onlineCount = jObj.getString("onlineCount");

				showToast(name + message + ". Currently " + onlineCount
						+ " people online!");

			} else if (flag.equalsIgnoreCase(TAG_MESSAGE)) {
				// if the flag is 'message', new message received
				String fromName = name;
				String message = jObj.getString("message");
				String sessionId = jObj.getString("sessionId");
				boolean isSelf = true;

				// Checking if the message was sent by you
				if (!sessionId.equals(utils.getSessionId())) {
					fromName = jObj.getString("name");
					isSelf = false;
				}

				Message m = new Message(fromName, message, isSelf);

				// Appending the message to chat list
				appendMessage(m);

			} else if (flag.equalsIgnoreCase(TAG_EXIT)) {
				// If the flag is 'exit', somebody left the conversation
				String name = jObj.getString("name");
				String message = jObj.getString("message");

				showToast(name + message);
			}

		} catch (JSONException e) {
			e.printStackTrace();
		}

	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		if(client != null & client.isConnected()){
			client.disconnect();
		}
	}

	/**
	 * Appending message to list view
	 * */
	private void appendMessage(final Message m) {
		runOnUiThread(new Runnable() {

			@Override
			public void run() {
				listMessages.add(m);

				adapter.notifyDataSetChanged();

				// Playing device's notification
				playBeep();
			}
		});
	}

	private void showToast(final String message) {

		runOnUiThread(new Runnable() {

			@Override
			public void run() {
				Toast.makeText(getApplicationContext(), message,
						Toast.LENGTH_LONG).show();
			}
		});

	}

	/**
	 * Plays device's default notification sound
	 * */
	public void playBeep() {

		try {
			Uri notification = RingtoneManager
					.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
			Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),
					notification);
			r.play();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

	public static String bytesToHex(byte[] bytes) {
		char[] hexChars = new char[bytes.length * 2];
		for (int j = 0; j < bytes.length; j++) {
			int v = bytes[j] & 0xFF;
			hexChars[j * 2] = hexArray[v >>> 4];
			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
		}
		return new String(hexChars);
	}

}



Now if you run the app, you can see the below screen as main activity output.

android building chat app like whatsapp

With this we have completed the android app part too.

7. Testing the Web and Android App

To test the android app you need two android mobiles or you can just use one android mobile and a web app. Follow below steps to test the android app.

1. Make sure that all your devices are connected to same wifi network. If you are using two android mobiles, connect them to same wifi network.

2. Get the ip address of the machine on which socket server is running. Follow 2nd step in Part1 tutorial to get the ip address of your machine.

3. Replace the ip address in WsConfig.java and main.js with your machine IP address.

4. Deploy the app on to android devices. If you are testing the app using web and android apps, open both the apps and test.

android-building-the-chat-app

The same conversation can be seen on web app too.

androd-chat-app-dad-mom-son

I hope everyone could able to build the app without any hurdles. If you have any queries or suggestions, please do let me know in the comment section below.

]]>
http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/feed/ 0
Android Building Group Chat App using Sockets – Part 1http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-1/ http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-1/#comments Thu, 30 Oct 2014 13:53:06 +0000 http://www.androidhive.info/?p=25512 We have seen a large number of applications come up in the recent past, to help us connect with each other across different mediums, like Hike, Whatsapp, Viber etc. You would be surprised to learn that its rather quite easy to develop one yourself. I thought providing an insight into developing such an application would be helpful for you guys.

Today we are going to learn how to build a simple group chat app using sockets. I won’t say this is the only way to build a chat app, but it is the quick & easiest way to build one. The best and efficient way would be using the push notifications instead of sockets.

android building chat app using java sockets



Overall we are going to build three components in this article. The first and important component is the socket server. The socket server plays a major role like handling the socket client connections, passing the messages between clients. Second component is the web app where you can join the chat conversation from a browser. Finally the android app. The main advantage of this app is, you can chat between web – web, web – android or android – android.

As this article seems pretty much lengthy, I am dividing the tutorial into two parts. In this first part, all the basic setup and building the web app is covered. In the 2nd Part, building the actual android app is covered.

Below are the final outcomes from this tutorial.

android building chat app like whatsapp


How the App Works Over Sockets?

If you are coming across the ‘sockets’ for the first time, the wikipedia page give you basic knowledge about socket communication. Below you can find a brief info about how our app works.

1. First we’ll have a socket server running. When the android app or web app connects to socket server, the server opens a TCP connection between server and client. The server is capable of opening concurrent connections when there are multiple clients.

2. When a socket connection is established few callback methods like onOpen, onMessage, onExit will be triggered on the both the ends (on server side and client side). There will be another method available to send message from client to server, or vice versa.

3. JSON strings will be exchanged between server and client as a communication medium. Each JSON contains a node called flag to identify the purpose of JSON message. Below is example of JSON when a client joined the conversation that contains the client name, session id and number of people online.

{
    "message": " joined conversation!",
    "flag": "new",
    "sessionId": "4",
    "name": "Ravi Tamada",
    "onlineCount": 6
}

4. Whenever a JSON message received on client side, the JSON will be parsed and appropriate action will be taken.

I hope the above information gave enough knowledge over the app. Now we can move forward and start building one by one component.


1. Eclipse adding J2EE & Tomcat 7 Support

The eclipse IDE that comes along with android SDK, doesn’t have J2EE and Tomcat server support. So we have to add J2EE and tomcat extensions. Another option would be downloading another eclipse that supports J2EE, but I would like use the eclipse that supports both android and j2ee instead of two different IDEs.

1. Download apache tomcat 7 from tomcat website. (You can download it from this direct link). Once downloaded, extract it in some location.

2. In Eclipse go to Help ⇒ Install New Software. Click on Work with drop down and select Juno – http://download.eclipse.org/releases/juno. (You might need to select the appropriate eclipse release depending upon your eclipse flavour)

3. Expand Web, XML, Java EE and OSGi Enterprise Development and select below extensions and proceed with installation.
   > Eclipse Java EE Developer Tools
   > JST Server Adapters
   > JST Server Adapters Extensions

4. Once the extensions are installed, Eclipse will prompt you to restart. When the eclipse re-opened, we need to create a server first. Goto Windows ⇒ Show View ⇒ Server ⇒ Servers.

5. In servers tab, click on new server wizard and select Apache ⇒ Tomcat v7.0 Server. Give server name, browse and select the tomcat home directory which we downloaded previously.

Check out the below video if you not very clear with the above steps.




2. Finding Your PC IP Address

As we need to test this app on multiple devices (it can be a mobile, PC or a laptop) in a wifi network, we need to know the IP address of the PC where the socket server project running. So instead of using localhost, we need to use the ip address in the url. In order to get the ip address of your machine, execute below commands in terminal.

On Windows

ipconfig
windows-os-getting-ip-address

On Mac

ifconfig
mac os getting system ip address

Note: The ip address of your machine might changes whenever you disconnected from wifi or a new device added to wifi network. So make sure that you are using the correct ip address every time you test the app.

Once the Eclipse Tomcat setup is ready and you know the IP address, you are good to go with socket server development. Building the socket server is very easy. The socket server we are going to build won’t take more than two class files.


3. Building the Socket Server

1. In Eclipse create a new Dynamic Web Project by navigating to File ⇒ New ⇒ Other ⇒ Web ⇒ Dynamic Web Project. Give the project name and select the Target runtime as Tomcat 7. I gave my project name as WebMobileGroupChatServer.

Once the project is created, it contains below directory structure.

j2ee web dynamic project directory structure

2. Right click on src ⇒ New ⇒ Package and give the package name. I gave my package name as info.androidhive.webmobilegroupchat.

3. Now download google-collections-0.8.jar, javaee-api-7.0.jar, json-org.jar files and paste them in project’s WebContent ⇒ WEB-INF ⇒ lib folder.

4. Create a new class named JSONUtils.java under project’s src package folder. This class contains methods to generate JSON strings those are required to have the communication b/w socket server and clients.

In the below code, if you observer each json contains flag node which tell the clients the purpose of JSON message. On the client side we have to take appropriate action considering the flag value.

Basically the flag contains four values.

self = This JSON contains the session information of that particular client. This will be the first json a client receives when it opens a sockets connection.

new = This JSON broadcasted to every client informing about the new client that is connected to socket server.

message = This contains the message sent by a client to server. Hence it will broadcasted to every client.

exit = The JSON informs every client about the client that is disconnected from the socket server.

package info.androidhive.webmobilegroupchat;

import org.json.JSONException;
import org.json.JSONObject;

public class JSONUtils {

	// flags to identify the kind of json response on client side
	private static final String FLAG_SELF = "self", FLAG_NEW = "new",
			FLAG_MESSAGE = "message", FLAG_EXIT = "exit";

	public JSONUtils() {
	}

	/**
	 * Json when client needs it's own session details
	 * */
	public String getClientDetailsJson(String sessionId, String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_SELF);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * Json to notify all the clients about new person joined
	 * */
	public String getNewClientJson(String sessionId, String name,
			String message, int onlineCount) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_NEW);
			jObj.put("name", name);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);
			jObj.put("onlineCount", onlineCount);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * Json when the client exits the socket connection
	 * */
	public String getClientExitJson(String sessionId, String name,
			String message, int onlineCount) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_EXIT);
			jObj.put("name", name);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);
			jObj.put("onlineCount", onlineCount);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * JSON when message needs to be sent to all the clients
	 * */
	public String getSendAllMessageJson(String sessionId, String fromName,
			String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_MESSAGE);
			jObj.put("sessionId", sessionId);
			jObj.put("name", fromName);
			jObj.put("message", message);

			json = jObj.toString();

		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}
}



5. Create another class named SocketServer.java and add the below code. This is the where we implement actual socket server.

This class mainly contains four callback methods.

onOpen() – This method is called when a new socket client connects.
onMessage() – This method is called when a new message received from the client.
onClose() – This method is called when a socket client disconnected from the server.
sendMessageToAll() – This method is used to broadcast a message to all socket clients.

package info.androidhive.webmobilegroupchat;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.json.JSONException;
import org.json.JSONObject;

import com.google.common.collect.Maps;

@ServerEndpoint("/chat")
public class SocketServer {

	// set to store all the live sessions
	private static final Set<Session> sessions = Collections
			.synchronizedSet(new HashSet<Session>());

	// Mapping between session and person name
	private static final HashMap<String, String> nameSessionPair = new HashMap<String, String>();

	private JSONUtils jsonUtils = new JSONUtils();

	// Getting query params
	public static Map<String, String> getQueryMap(String query) {
		Map<String, String> map = Maps.newHashMap();
		if (query != null) {
			String[] params = query.split("&");
			for (String param : params) {
				String[] nameval = param.split("=");
				map.put(nameval[0], nameval[1]);
			}
		}
		return map;
	}

	/**
	 * Called when a socket connection opened
	 * */
	@OnOpen
	public void onOpen(Session session) {

		System.out.println(session.getId() + " has opened a connection");

		Map<String, String> queryParams = getQueryMap(session.getQueryString());

		String name = "";

		if (queryParams.containsKey("name")) {

			// Getting client name via query param
			name = queryParams.get("name");
			try {
				name = URLDecoder.decode(name, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}

			// Mapping client name and session id
			nameSessionPair.put(session.getId(), name);
		}

		// Adding session to session list
		sessions.add(session);

		try {
			// Sending session id to the client that just connected
			session.getBasicRemote().sendText(
					jsonUtils.getClientDetailsJson(session.getId(),
							"Your session details"));
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Notifying all the clients about new person joined
		sendMessageToAll(session.getId(), name, " joined conversation!", true,
				false);

	}

	/**
	 * method called when new message received from any client
	 * 
	 * @param message
	 *            JSON message from client
	 * */
	@OnMessage
	public void onMessage(String message, Session session) {

		System.out.println("Message from " + session.getId() + ": " + message);

		String msg = null;

		// Parsing the json and getting message
		try {
			JSONObject jObj = new JSONObject(message);
			msg = jObj.getString("message");
		} catch (JSONException e) {
			e.printStackTrace();
		}

		// Sending the message to all clients
		sendMessageToAll(session.getId(), nameSessionPair.get(session.getId()),
				msg, false, false);
	}

	/**
	 * Method called when a connection is closed
	 * */
	@OnClose
	public void onClose(Session session) {

		System.out.println("Session " + session.getId() + " has ended");

		// Getting the client name that exited
		String name = nameSessionPair.get(session.getId());

		// removing the session from sessions list
		sessions.remove(session);

		// Notifying all the clients about person exit
		sendMessageToAll(session.getId(), name, " left conversation!", false,
				true);

	}

	/**
	 * Method to send message to all clients
	 * 
	 * @param sessionId
	 * @param message
	 *            message to be sent to clients
	 * @param isNewClient
	 *            flag to identify that message is about new person joined
	 * @param isExit
	 *            flag to identify that a person left the conversation
	 * */
	private void sendMessageToAll(String sessionId, String name,
			String message, boolean isNewClient, boolean isExit) {

		// Looping through all the sessions and sending the message individually
		for (Session s : sessions) {
			String json = null;

			// Checking if the message is about new client joined
			if (isNewClient) {
				json = jsonUtils.getNewClientJson(sessionId, name, message,
						sessions.size());

			} else if (isExit) {
				// Checking if the person left the conversation
				json = jsonUtils.getClientExitJson(sessionId, name, message,
						sessions.size());
			} else {
				// Normal chat conversation message
				json = jsonUtils
						.getSendAllMessageJson(sessionId, name, message);
			}

			try {
				System.out.println("Sending Message To: " + sessionId + ", "
						+ json);

				s.getBasicRemote().sendText(json);
			} catch (IOException e) {
				System.out.println("error in sending. " + s.getId() + ", "
						+ e.getMessage());
				e.printStackTrace();
			}
		}
	}
}



With this we have completed the socket server part. Now quickly we can build a web app to the test the socket server. Again building the web app is very simple. The complete web app can be built using basic web technologies like HTML, CSS & jQuery.


4. Building The Web App (HTML, CSS & jQuery)

To create the web app, we don’t have to create another project. This is the part of same socket server project, so follow the below steps in the same project.

1. Create a file named style.css under WebContent ⇒ WEB-INF folder. This contains the css styles for the web UI.

body {
	padding: 0;
	margin: 0;
}

.body_container {
	width: 1000px;
	margin: 0 auto;
	padding: 0;
}

.clear {
	clear: both;
}

.green {
	color: #8aaf0d;
}

#header {
	margin: 0 auto;
	padding: 50px 0;
	text-align: center;
}

#header h1,#header p.online_count {
	font-family: 'Open Sans', sans-serif;
	font-weight: 300;
}

#header p.online_count {
	font-size: 18px;
	display: none;
}

.box_shadow {
	background: #f3f4f6;
	border: 1px solid #e4e4e4;
	-moz-box-shadow: 0px 0px 2px 1px #e5e5e5;
	-webkit-box-shadow: 0px 0px 2px 1px #e5e5e5;
	box-shadow: 0px 0px 2px 1px #e5e5e5;
}

#prompt_name_container {
	margin: 0 auto;
	width: 350px;
	text-align: center;
}

#prompt_name_container p {
	font-family: 'Open Sans', sans-serif;
	font-weight: 300;
	font-size: 24px;
	color: #5e5e5e;
}

#prompt_name_container #input_name {
	border: 1px solid #dddddd;
	padding: 10px;
	width: 250px;
	display: block;
	margin: 0 auto;
	outline: none;
	font-family: 'Open Sans', sans-serif;
}

#prompt_name_container #btn_join {
	border: none;
	width: 100px;
	display: block;
	outline: none;
	font-family: 'Open Sans', sans-serif;
	color: #fff;
	background: #96be0e;
	font-size: 18px;
	padding: 5px 20px;
	margin: 15px auto;
	cursor: pointer;
}

#message_container {
	display: none;
	width: 500px;
	margin: 0 auto;
	background: #fff;
	padding: 15px 0 0 0;
}

#messages {
	margin: 0;
	padding: 0;
	height: 300px;
	overflow: scroll;
	overflow-x: hidden;
}

#messages li {
	list-style: none;
	font-family: 'Open Sans', sans-serif;
	font-size: 16px;
	padding: 10px 20px;
}

#messages li.new,#messages li.exit {
	font-style: italic;
	color: #bbbbbb;
}

#messages li span.name {
	color: #8aaf0d;
}

#messages li span.red {
	color: #e94e59;
}

#input_message_container {
	margin: 40px 20px 0 20px;
}

#input_message {
	background: #f0f0f0;
	border: none;
	font-size: 20px;
	font-family: 'Open Sans', sans-serif;
	outline: none;
	padding: 10px;
	float: left;
	margin: 0;
	width: 348px;
}

#btn_send {
	float: left;
	margin: 0;
	border: none;
	color: #fff;
	font-family: 'Open Sans', sans-serif;
	background: #96be0e;
	outline: none;
	padding: 10px 20px;
	font-size: 20px;
	cursor: pointer;
}

#btn_close {
	margin: 0;
	border: none;
	color: #fff;
	font-family: 'Open Sans', sans-serif;
	background: #e94e59;
	outline: none;
	padding: 10px 20px;
	font-size: 20px;
	cursor: pointer;
	width: 100%;
	margin: 30px 0 0 0;
}



2. Create another file named main.js and add below javascript. This file contains all the methods required to handle communication between socket server and client. The other things like parsing JSON, appending messages to chat list also taken care in the same file.

// to keep the session id
var sessionId = '';

// name of the client
var name = '';

// socket connection url and port
var socket_url = '192.168.0.102';
var port = '8080';

$(document).ready(function() {

	$("#form_submit, #form_send_message").submit(function(e) {
		e.preventDefault();
		join();
	});
});

var webSocket;

/**
 * Connecting to socket
 */
function join() {
	// Checking person name
	if ($('#input_name').val().trim().length <= 0) {
		alert('Enter your name');
	} else {
		name = $('#input_name').val().trim();

		$('#prompt_name_container').fadeOut(1000, function() {
			// opening socket connection
			openSocket();
		});
	}

	return false;
}

/**
 * Will open the socket connection
 */
function openSocket() {
	// Ensures only one connection is open at a time
	if (webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED) {
		return;
	}

	// Create a new instance of the websocket
	webSocket = new WebSocket("ws://" + socket_url + ":" + port
			+ "/WebMobileGroupChatServer/chat?name=" + name);

	/**
	 * Binds functions to the listeners for the websocket.
	 */
	webSocket.onopen = function(event) {
		$('#message_container').fadeIn();

		if (event.data === undefined)
			return;

	};

	webSocket.onmessage = function(event) {

		// parsing the json data
		parseMessage(event.data);
	};

	webSocket.onclose = function(event) {
		alert('Error! Connection is closed. Try connecting again.');
	};
}

/**
 * Sending the chat message to server
 */
function send() {
	var message = $('#input_message').val();

	if (message.trim().length > 0) {
		sendMessageToServer('message', message);
	} else {
		alert('Please enter message to send!');
	}

}

/**
 * Closing the socket connection
 */
function closeSocket() {
	webSocket.close();

	$('#message_container').fadeOut(600, function() {
		$('#prompt_name_container').fadeIn();
		// clearing the name and session id
		sessionId = '';
		name = '';

		// clear the ul li messages
		$('#messages').html('');
		$('p.online_count').hide();
	});
}

/**
 * Parsing the json message. The type of message is identified by 'flag' node
 * value flag can be self, new, message, exit
 */
function parseMessage(message) {
	var jObj = $.parseJSON(message);

	// if the flag is 'self' message contains the session id
	if (jObj.flag == 'self') {

		sessionId = jObj.sessionId;

	} else if (jObj.flag == 'new') {
		// if the flag is 'new', a client joined the chat room
		var new_name = 'You';

		// number of people online
		var online_count = jObj.onlineCount;

		$('p.online_count').html(
				'Hello, <span class="green">' + name + '</span>. <b>'
						+ online_count + '</b> people online right now')
				.fadeIn();

		if (jObj.sessionId != sessionId) {
			new_name = jObj.name;
		}

		var li = '<li class="new"><span class="name">' + new_name + '</span> '
				+ jObj.message + '</li>';
		$('#messages').append(li);

		$('#input_message').val('');

	} else if (jObj.flag == 'message') {
		// if the json flag is 'message', it means somebody sent the chat
		// message

		var from_name = 'You';

		if (jObj.sessionId != sessionId) {
			from_name = jObj.name;
		}

		var li = '<li><span class="name">' + from_name + '</span> '
				+ jObj.message + '</li>';

		// appending the chat message to list
		appendChatMessage(li);

		$('#input_message').val('');

	} else if (jObj.flag == 'exit') {
		// if the json flag is 'exit', it means somebody left the chat room
		var li = '<li class="exit"><span class="name red">' + jObj.name
				+ '</span> ' + jObj.message + '</li>';

		var online_count = jObj.onlineCount;

		$('p.online_count').html(
				'Hello, <span class="green">' + name + '</span>. <b>'
						+ online_count + '</b> people online right now');

		appendChatMessage(li);
	}
}

/**
 * Appending the chat message to list
 */
function appendChatMessage(li) {
	$('#messages').append(li);

	// scrolling the list to bottom so that new message will be visible
	$('#messages').scrollTop($('#messages').height());
}

/**
 * Sending message to socket server message will be in json format
 */
function sendMessageToServer(flag, message) {
	var json = '{""}';

	// preparing json object
	var myObject = new Object();
	myObject.sessionId = sessionId;
	myObject.message = message;
	myObject.flag = flag;

	// converting json object to json string
	json = JSON.stringify(myObject);

	// sending message to server
	webSocket.send(json);
}

3. Now download jquery-1.11.1.min and the paste the file in WebContent ⇒ WEB-INF.

4. Finally create another file named index.html and add below code.

<!DOCTYPE html>

<html>
<head>
<title>Android, WebSockets Chat App | AndroidHive
	(www.androidhive.info)</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<link
	href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700'
	rel='stylesheet' type='text/css'>
<link href="style.css" type="text/css" rel='stylesheet' />
<script type="text/javascript" src="main.js"></script>
</head>
<body>
	<div class="body_container">

		<div id="header">
			<h1>Android WebSockets Chat Application</h1>
			<p class='online_count'>
				<b>23</b> people online right now
			</p>
		</div>

		<div id="prompt_name_container" class="box_shadow">
			<p>Enter your name</p>
			<form id="form_submit" method="post">
				<input type="text" id="input_name" /> <input type="submit"
					value="JOIN" id="btn_join">
			</form>
		</div>

		<div id="message_container" class="box_shadow">

			<ul id="messages">
			</ul>


			<div id="input_message_container">
				<form id="form_send_message" method="post" action="#">
					<input type="text" id="input_message"
						placeholder="Type your message here..." /> <input type="submit"
						id="btn_send" onclick="send();" value="Send" />
					<div class="clear"></div>
				</form>
			</div>
			<div>

				<input type="button" onclick="closeSocket();"
					value="Leave Chat Room" id="btn_close" />
			</div>

		</div>

	</div>

</body>
</html>



5. Now run the project by Right Click on project ⇒ Run As ⇒ Run on Server. You can see the project running on http://localhost:8080/WebMobileGroupChatServer/


5. Testing The Socket Server (using the web app)

In order to test the socket server using the web app, follow below steps. You can use multiple devices (like desktop PC, Laptop) or just one machine is enough.

1. Make sure that all the machines connected to same wifi router if you are testing the app on multiple machines. If you are testing the app using a single computer, you don’t have to connect to a wifi network.

2. Find the IP address of the machine on which socket server project is running. (Follow the steps mentioned in 2nd point to get the ip address)

3. Replace the ip address in main.js with your machine’s ip address.

var socket_url = '_YOUR_IP_ADDRESS_';

4. Now access your project url in browser. Replace localhost with your machine ip address in the url. My project url is http://192.168.0.104:8080/WebMobileGroupChatServer/. Access same url in another browser software or another machine’s browser to chat with different machines.


Once you are able to chat between multiple clients, we can go forward and build the android app in Android Building Group Chat App using Sockets – Part 2

]]>
http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-1/feed/ 0
MoboMarket Releases its 3.0 Version on its 2-year Anniversaryhttp://www.androidhive.info/2014/10/mobomarket-releases-its-3-0-version-on-its-2-year-anniversary/ http://www.androidhive.info/2014/10/mobomarket-releases-its-3-0-version-on-its-2-year-anniversary/#comments Sun, 26 Oct 2014 08:01:18 +0000 http://www.androidhive.info/?p=25685 If you are a geek or an Android nosy parker, you would not be unaware of MoboMarket – an amazing Android marketplace and an extra-ordinary substitute to the Google Play Store. Designed and developed by Moborobo.com, one of the acclaimed Android application developers over the globe, MoboMarket is home to thousands of outstanding applications and games that are unavailable in the default play store.

mobomarket-banner

Since each and every Android phone today comes with an inbuilt Google Play Store marketplace, very few people today are aware of other marketplaces available to download apps from. And MoboMarket is such a marketplace. Developed with an inception to provide a platform for all the Android app developers around the globe, the store today boasts of hundred thousands of free applications to download from.

2

Google Play Store taking advantage of its monopoly over the Android devices, does not allow each and every application to be listed in its store. Thus, huge number of applications were rejected and refrained from any marketplace to bring them to the users. And hence MoboMarket was developed to help developers worldwide to publish their masterpieces in this store. Once listed with MoboMarket, it takes care of all your apps and delivers highest-quality users to your app thus maximizing your app revenue potential.

Ever since the inception, MoboMarket received huge response from developers worldwide along with active Android users. After two years of consistent efforts and impeccable service, the marketplace today boasts of over 100 million users worldwide. Moreover, the store has 40 million of daily distribution and 5 million of daily active users. Today, most of MoboMarket’s active users come from North America, South East Asia, Middle East and North Africa.

1

Easy to use and well organized, MoboMarket place is more than a mere Android store and offers you a plethora of exciting features. And one of its exclusive features includes the geo-location targeting and content pushing system. This system helps the users with the most relevant applications based on their location and frequent searches. With the geo-location targeting feature the store helps you with apps that are related to your location thus getting you amazing offers, events, restaurant options, etc.

Recently, MoboMarket completed two successful years and announced the release of its 3.0 version on its 2nd Anniversary. The spokesperson of Moborobo.com confirmed many exciting upgrades and features that make this store all the more amazing. With the all new version, MoboMarket gets a more improved geo-location and content pushing system, brand new user friendly interface, and the user interest based search and recommendation system.

3

The earlier version of MoboMarket did not require any sign-up processes to use the store like that in the Google Play Store. This remains the same in the version 3.0. Moreover the version has become more intuitive and smooth thus responding well to swipes and taps. The more optimized and organized store further delivers the content you are looking for with a lightning fast speed. User can just navigate through thousands of popular apps and install them all simultaneously with just a touch of a finger.

So, what are you waiting for? Get your own MoboMarket marketplace for MoboRobo’s official website and experience the all new Android applications that cannot be found in the Google Play Store.

Website: http://www.moborobo.com/
Facebook Page: https://www.facebook.com/MoboroboEN
https://www.facebook.com/MoboMarketEN
Twitter: https://twitter.com/Moborobo_EN

download


]]>
http://www.androidhive.info/2014/10/mobomarket-releases-its-3-0-version-on-its-2-year-anniversary/feed/ 0
How to Create Google Glass Options Menuhttp://www.androidhive.info/2014/10/how-to-create-google-glass-options-menu/ http://www.androidhive.info/2014/10/how-to-create-google-glass-options-menu/#comments Wed, 08 Oct 2014 15:03:41 +0000 http://www.androidhive.info/?p=25348 My previous tutorial about google glass explains the basic setup required for google glass app development.

In this tutorial I am going to explain how to display menu items on google glass. Displaying options menu on glass is very simple. The menu we are going to create contains three menu items where selecting each menu items performs separate action.

google glass options menu



Let’s starts this by creating a sample glass project.

Creating new Glass Project

1. Create a new project in Eclipse. File ⇒ New ⇒ Android Application Project and give application name, project name and package.

2. Set Minimum Required SDK and Target SDK to API 19: Android 4.4 (KitKat), Compile With to Glass Development Kit Sneak Peek (Google Inc.) (API19) and select the Theme to None

3. Download this drawable folder and paste it in project’s res folder. This folder contains icons required for the menu items.

4. Open strings.xml and add below strings.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Glass Menu</string>     
    
    <!-- App start voice command -->
    <string name="start_command">Menus</string>
    
    <!-- View messages -->
    <string name="msg_tap_to_menu">Please tap to lauch "menu"</string>
    <string name="msg_about">Activity for about</string>
    <string name="msg_url">www.androidhive.info</string>
    <string name="msg_settings">Activity for settings</string>
    
    <!-- Menu Items -->
    <string name="action_about">About</string>
    <string name="action_settings">Settings</string>
    <string name="action_quit">Quit</string>
    

</resources>



5. Now create an xml file named main.xml under res ⇒ menu folder. In this file we declare actual menu items. We are going to add three menu items about, settings and quit. The first two menu items will launch two separate activities and the third option will quit the app.

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    
    <item
        android:id="@+id/action_about"
        android:icon="@drawable/ico_about"        
        android:title="@string/action_about"/>
    
    <item
        android:id="@+id/action_settings"
        android:icon="@drawable/ico_settings"        
        android:title="@string/action_settings"/>
    
    <item
        android:id="@+id/action_quit"
        android:icon="@drawable/ico_quit"        
        android:title="@string/action_quit"/>

</menu>



6. Now quickly create two activities for about and settings. These activities will be launched when menu items are selected.

Create activity_about.xml and AboutActivity.java and paste below code.

<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=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/msg_about"
        android:textSize="35dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/msg_url"
        android:textSize="20dp" />

</RelativeLayout>
package info.androidhive.glassmenu;

import android.app.Activity;
import android.os.Bundle;

public class AboutActivity extends Activity {

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



Also create activity_settings.xml and SettingsActivity.java and paste below code.

<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=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/msg_settings"
        android:textSize="35dp" />

</RelativeLayout>
package info.androidhive.glassmenu;

import android.app.Activity;
import android.os.Bundle;

public class SettingsActivity extends Activity {

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


7. Now open your main activity class (MainActivity.java) and do the below changes. On Glass, menus can be triggered using the same methods which we use when developing mobile app.

Here user tap on D-Pad is detected using KEYCODE_DPAD_CENTER key event inside onKeyDown() method. Then by calling openOptionsMenu() method, menu is rendered on to glass screen. Finally the appropriate action for menu item selection is taken place in onOptionsItemSelected() method.

package info.androidhive.glassmenu;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {

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

	/**
	 * Opening menu on tapping on D-pad
	 * */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
			openOptionsMenu();
			return true;
		}
		return false;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	/**
	 * Taking appropriate action on selecting menu item
	 * */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		
		switch (item.getItemId()) {
		case R.id.action_about:
			// About menu item selected
			Intent ia = new Intent(MainActivity.this, AboutActivity.class);
			startActivity(ia);
			return true;
		case R.id.action_settings:
			// Settings menu item selected
			Intent is = new Intent(MainActivity.this, SettingsActivity.class);
			startActivity(is);
			return true;
		case R.id.action_quit:
			// Quit menu item selected
			// we'll simply close the app
			finish();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

}

Until now we are done with actual menus part. But before testing it, I would like to add the app to ok glass menu to launch the app by voice command.

Adding the app to “Ok Glass” menu

8. Create a new folder named xml under res folder.

9. Inside res ⇒ xml folder create an xml file named voice_trigger_start.xml.

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/start_command" />

10. Now add DEVELOPMENT permission in the AndroidManifest.xml. This allows us to use unlisted glass voice commands.

To launch the app by voice command, we need to add intent-filter and meta-data by adding appropriate voice packages.

<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.glassmenu"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    
    <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.glassmenu.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>

            <meta-data
                android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/voice_trigger_start" />
        </activity>
        <activity
            android:name="info.androidhive.glassmenu.AboutActivity"
            android:label="@string/app_name" />
        <activity
            android:name="info.androidhive.glassmenu.SettingsActivity"
            android:label="@string/app_name" />
    </application>

</manifest>

Now deploy the app on to Google Glass and launch the app by saying Ok Glass Menus. Once the app is launched, if you tap on D-Pad you can the menu opened. You can swipe forward / backward to see other menu options.

google glass options menu
]]>
http://www.androidhive.info/2014/10/how-to-create-google-glass-options-menu/feed/ 0
Getting started with Google Glass Developmenthttp://www.androidhive.info/2014/09/getting-started-with-google-glass-development/ http://www.androidhive.info/2014/09/getting-started-with-google-glass-development/#comments Sat, 20 Sep 2014 06:21:18 +0000 http://www.androidhive.info/?p=25115 Today I am very excited to write my first Google Glass tutorial. This is the first article on android wearable devices which is a simple Hello World app. Going forward I’ll cover other android wearables like watches.

I know that Google Glass is very expensive and not everybody afford to have one. But I am sure the price will comedown once the glass is stable and more popular.

As of today there is no official glass emulator to test the apps. But you can try out these options discussed in this forum.

If you are really serious about building apps for glass, you can order it from PlayStore.

google glass hello world program

The following steps will take you through various stages of glass development like downloading GDK, installing drivers, writing a simple hello world program and deploying the same on glass.

1. Downloading GDK (Glass Development Kit)

Glass Development Kit provides APIs to build glassware applications. These APIs won’t be available in all the versions of android.

1 Open SDK manager from your android’s SDK folder.

2 Select Android 4.2.2 (API 19), Google USB Driver (under Extras) and click on Install Packages. This takes a while to download all the packages.

android installing google glass gdk

2. Installing Google Glass Drivers

Android device drivers can be installed either from android SDK’s google usb drivers or with the software that comes from device manufacturer. For Google Glass we can directly use google usb driver. But before installing we need to do a modification in android_winusb.inf, otherwise, your glass device won’t be listed in Eclipse even though you have installed the drivers correctly.

1. Goto your android_SDK_folder\sdk\extras\google\usb_driver and edit android_winusb.inf. Add the below text at the end of the file.

[Google.NTamd64]

;GoogleGlass
%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_4E11&REV_0216
%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E11&MI_01
%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_9001&REV_0216
%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_9001&MI_01

[Google.NTx86]

;GoogleGlass
%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_4E11&REV_0216
%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_4E11&MI_01
%SingleAdbInterface%        = USB_Install, USB\VID_18D1&PID_9001&REV_0216
%CompositeAdbInterface%     = USB_Install, USB\VID_18D1&PID_9001&MI_01

2. Now open Run (shortcut Win + R) and type devmgmt.msc and press ok. This opens up Device Manager window.

3. In device manager right click on your Google Glass device and click install drivers. When it ask for browse location, select android_winusb.inf parent folder and follow the instructions.

Following links will help you if you got into any issue.
Google USB Driver
Installing USB Driver
Stackoverflow Discussion


3. Creating new Glass Project

Creating a glass project is same as usual android project, but it differs in choosing proper API version.

1. Create a new project in Eclipse. File ⇒ New ⇒ Android Application Project and give application name, project name and package.

2. Set Minimum Required SDK and Target SDK to API 19: Android 4.4 (KitKat), Compile With to Glass Development Kit Sneak Peek (Google Inc.) (API19) and select the Theme to None

3. Once the project is created open AndroidManifest.xml file and remove the theme android:theme property. By removing this property, glass will apply it’s own theme.

4. Open strings.xml and add below string values.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <string name="app_name">Hello Glass</string>
    <string name="action_settings">Settings</string>
    
    <!-- main activity strings -->
    <string name="hello_glass">Hello Glass !</string>
    <string name="home_url">www.androidhive.info</string>
    
    <!-- "ok glass" voice command -->
    <string name="start_command">Hello Glass</string>    

</resources>



5. Now open your main activity layout file and paste the following code. In my case my activity layout is activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_centerInParent="true"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_glass" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/home_url"
        android:textSize="20dp" />

</LinearLayout>



6. I haven’t modified anything in MainActivity.java. Your main activity should look like below.

package info.androidhive.helloglass;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

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

Until now we are done with simple hello world program. In the next step we’ll learn how to deploy the app on the glass device.

4. Running the application on Glass

7. In order to deploy the app on Glass we need to turn on debug option. So on your glass go to Settings ⇒ Device Info ⇒ Turn on debug.

8. Now connect the Glass to your PC using a USB cable. Right click on the project and Run As ⇒ Android Application. Eclipse will list the glass in the list of devices available. Select the glass device and press ok. You should able see the app running on your glass.

google glass hello world program

Now you have successfully developed your first Hello Glass application. But if you close the app or glass goes to sleep, you can’t find the app again anywhere on glass.

In the next step we’ll learn how to add our app to “Ok Glass” menu. So that we can launch our app using a voice command like other glass apps.

5. Adding our app to “Ok Glass” menu

9. Download app_icon.png and paste it in res ⇒ drawable folder. As per glass guidelines, app icon should be 50x50px with white foreground and transparent background.

10. Create a new folder named xml under res folder.

11. Inside res ⇒ xml folder create an xml file named voice_trigger_start.xml.

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/start_command" />

12. Now add DEVELOPMENT permission in the AndroidManifest.xml which allows us to use unlisted glass voice commands.

Also we need to modify the intent-filter and a meta-data tag for the main activity by adding VOICE_TRIGGER properties.

<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.helloglass"
    android:versionCode="1"
    android:versionName="1.0" >

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

    <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.helloglass.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>

            <meta-data
                android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/voice_trigger_start" />
        </activity>
    </application>

</manifest>

Now deploy the app once again on the glass. This time app won’t be launched automatically. You have to launch the app by saying Ok Glass Hello Glass command.

google-glass-ok-glass-menu
]]>
http://www.androidhive.info/2014/09/getting-started-with-google-glass-development/feed/ 0
Housing.com Android App: Hunting properties has never been so easyhttp://www.androidhive.info/2014/09/housing-com-android-app-hunting-properties-has-never-been-so-easy/ http://www.androidhive.info/2014/09/housing-com-android-app-hunting-properties-has-never-been-so-easy/#comments Wed, 10 Sep 2014 12:42:02 +0000 http://www.androidhive.info/?p=23111 For those who have to shift places frequently, the most cumbersome task will be finding a good home, in spite of the fact that there might be thousands of vacant homes, paying guest opportunities and flats in the specific region. More often than not, you get misleading information about properties and spend a lot of money in following such deals. In the long run, however, you end up in complete regression. In addition, it isn’t safe to completely depend upon those brokers, as they might fool you for their profit. For instance, if you are looking for resale flats in Pune, you might see that many options are available, but only a few of them might be ‘real’, particularly in terms of facilities and features. Nevertheless, an Android app from Housing.com — a revolutionary web-based platform to find the dream home for your different purposes, in a matter of a few clicks — is all set to change the way you deal these things! In the article, we will have a fully-fledged review of Housing.com Android application in such a way that you can understand why you should use Housing.com for Android instead of those conventional methods. First, we’ll have an introduction on Housing.com and also at its property search app for Android.

Housing.com Android App


An Introduction to Housing.com & its Android App

If you are an active on social media, you might have heard of Housing.com, which is an emerging platform for online real estate dealings. Conceived and built by a few IIT Alumni, Housing.com did not take that time to grab limelight. In addition to this innovative approach when it comes to the aspect of User Interface and representation of data, major attractive factor of Housing.com is its credibility of data. For instance, when you are checking out listings that you do see in Housing.com, let it be flats, homes, hostels, paying guest opportunities or anything else, you can be sure that even minute details of a listing, such as number of bed rooms or availability of features, will be extremely accurate in every possible manner. Thanks to the developers of this android app, Housing.com for Android does not need you to compromise on quality when compared to its main platform. Thus, despite the fact that you are depending upon one mobile device, you can get the same, unadulterated Housing.com experience. Now, we will have an overview of this application, covering its main functions and other aspects.

As we said earlier, Housing.com for Android is the compressed version of Housing.com, which is meant to work great in Android-powered Smartphones and Tablet PCs. To understand things clearly, we will talk in the ‘practical way’. For instance, suppose you have to attend an interview in Hyderabad and you want to find a good hostel or short stay PG accommodations in that specified area, as you have no other way outs. However, you could not use your PC to find the best hotel in the area. In such situations, you can install Housing.com App in your Android Smartphone or Tablet PC and use its wonderful search feature to find the best option among hundreds of real estate listings. Now, we hope you have a clearer idea on practical instances when Housing.com for Android will be helpful. So, we will move on to noticeable features and other aspects like UI of this application. Even before we begin, we do assure you that Housing.com is the best choice when you are looking for an effective property app.


Finding your Dream Home

Using Housing.com app for finding your dream home is a comparatively easy task. We had used this application, and we were really impressed by its outstanding User Interface that allows users find their dream home without having to face any sort of glitches. In order to find a home or hostel in a specific place, you have to follow some simple steps in Housing.com for Android. First, from the thumbnail-based view, you have to select the city, in which you’d like to find your dream home. Currently, the app supports a number of cities such as Delhi and Hyderabad. Once you have selected the city, you can see a page consisting of a search engine-like UI. In that page, you have to provide desired region of city, either through GPS or selecting from shown list, and perhaps some basic requirements such as number of bedroom. In addition, you have to tell the app, which option you are looking for — rent, buy, agent, or Paying Guest vacancy.

housing searching locality

Then, you have to hit the Search button, upon which you will be shown listing results that match your requirements. Just as it has been shown in Housing.com main website, you can see websites in two forms — in a visual map and as lists. You can use the method according to your convenience, and once you have done, it is all about filtering those results to find the optimum dream home of yours.

housing map view

There are a number of criteria to find the best home. For instance, if you are looking forward to find a paying guest in Gurgaon, you can filter your results in different ways such as rent range, BHK, availability of features like meals, internet, price per person, availability of furnishing, other features, etc. Similarly, when you are on ‘results’ page, you can list those results to according its price range and other aspects. Altogether, the home-finding process of this app is something great, and we had nothing to be disappointed of.


Other Features

• Accuracy & Credibility of Data: What makes Housing.com and its application much popular than others is the fact that Housing.com lists real estate deals only after checking their credibility in every possible way. So, if you see that a Paying Guest opportunity offers internet and meals facility, you can be sure of it, because the team of Housing.com would have confirmed all these aspects before making it live in Housing.com and thus its application.

• Simple UI: As a whole, User Interface of Housing.com is something great, particularly considering its sections for filtering results and posting requirements. Even if you are a newbie to Android, you would find no sort of difficulty in finding your dream home using this app.

housing list property


Our Verdict

While considering all these factors, we find no reasons not to prefer Housing.com for Android to anyone! Especially, we loved its wonderful User Interface and credibility of published data. So, we would prefer this app to everyone, who is looking for the best home out there.

download
]]>
http://www.androidhive.info/2014/09/housing-com-android-app-hunting-properties-has-never-been-so-easy/feed/ 0
Android JSON parsing using Volleyhttp://www.androidhive.info/2014/09/android-json-parsing-using-volley/ http://www.androidhive.info/2014/09/android-json-parsing-using-volley/#comments Sat, 06 Sep 2014 12:53:03 +0000 http://www.androidhive.info/?p=22318 My previous article Android JSON Parsing Tutorial explains parsing json in a simple manner which got very good feedback and good ranking in search engines. In this tutorial I want to explain the same but in a easy & robust way using volley library.

If you want to know more about Volley and it’s benefits, go through Android working with Volley Library.

android json parsing using volley



So let’s start this with a simple project.

Creating New Project

1. Create a new project in Eclipse from File ⇒ New ⇒ Android Application Project and fill all the required details.

I gave my project name as VolleyJson and package name as info.androidhive.volleyjson

2. Now create a new package under src folder by Right clicking on src ⇒ New ⇒ Package and give the package name as app. So my new package name will be info.androidhive.volleyjson.app

3. Download volley.jar and paste it in project’s libs folder.

4. Create a new class named AppController.java under app package. This is going to be a singleton class where we initialize all the volley core objects.

package info.androidhive.volleyjson.app;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
 
public class AppController extends Application {
 
    public static final String TAG = AppController.class.getSimpleName();
 
    private RequestQueue mRequestQueue;
 
    private static AppController mInstance;
 
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }
 
    public static synchronized AppController getInstance() {
        return mInstance;
    }
 
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }
 
        return mRequestQueue;
    }
 
    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }
 
    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }
 
    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}


5. AppController.java has to be executed when the app is launched. So add this class in your AndroidManifest.xml using name attribute for <application> tag.

Also add INTERNET permission as we need to make internet calls from the app.

<application
        android:name="info.androidhive.volleyjson.app.AppController">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.volleyjson"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    
    <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" 
        android:name="info.androidhive.volleyjson.app.AppController">
        <activity
            android:name="info.androidhive.volleyjson.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>



6. Open the layout file of main activity and add below code. (In my case my main activity layout file is activity_main.xml).

In this layout we are adding two Buttons and a TextView. In two Button, one is to invoke json object request and other is to invoke json array request. The TextView is used to display the parsed json response.

<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"
    tools:context="${relativePackage}.${activityClass}" >

    <Button
        android:id="@+id/btnObjRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:text="Make JSON Object Request" />

    <Button
        android:id="@+id/btnArrayRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:text="Make JSON Array Request"
        android:layout_below="@id/btnObjRequest" />
    
    <TextView
        android:id="@+id/txtResponse"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btnArrayRequest"
        android:layout_marginTop="40px"
        android:padding="20dp" />

</RelativeLayout>


7. Now open main activity class MainActivity.java and add basic code like importing UI elements, adding button click events and initializing other objects.

Below you can notice two methods makeJsonObjectRequest() and makeJsonArrayRequest(). But these methods left empty for now, we’ll add code for these methods in next steps.

package info.androidhive.volleyjson;

import info.androidhive.volleyjson.R;
import info.androidhive.volleyjson.app.AppController;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {

	// json object response url
	private String urlJsonObj = "http://api.androidhive.info/volley/person_object.json";
	
	// json array response url
	private String urlJsonArry = "http://api.androidhive.info/volley/person_array.json";

	private static String TAG = MainActivity.class.getSimpleName();
	private Button btnMakeObjectRequest, btnMakeArrayRequest;

	// Progress dialog
	private ProgressDialog pDialog;

	private TextView txtResponse;

	// temporary string to show the parsed response
	private String jsonResponse;

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

		btnMakeObjectRequest = (Button) findViewById(R.id.btnObjRequest);
		btnMakeArrayRequest = (Button) findViewById(R.id.btnArrayRequest);
		txtResponse = (TextView) findViewById(R.id.txtResponse);

		pDialog = new ProgressDialog(this);
		pDialog.setMessage("Please wait...");
		pDialog.setCancelable(false);

		btnMakeObjectRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json object request
				makeJsonObjectRequest();
			}
		});

		btnMakeArrayRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json array request
				makeJsonArrayRequest();
			}
		});

	}

	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {
	}

	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}
}



Normally json response will be of two types. It can be either json object or json array. If the json starts with {, it is considered to be JSON Object. As well if the json starts with [, then it is JSON Array.

Now we'll see how to make these requests individually.

Making JSON Object Request

8. Volley provides JsonObjectRequest class to make json object request. Add the below code in makeJsonObjectRequest() method. Here we are fetching the json by making a call to below url and parsing it. Finally the parsed response is appended to a string and displayed on the screen.

Sample JSON Object Response

URL: http://api.androidhive.info/volley/person_object.json

{
	"name" : "Ravi Tamada", 
	"email" : "ravi8x@gmail.com",
	"phone" : {
		"home" : "08947 000000",
		"mobile" : "9999999999"
	}
	
}
	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {

		showpDialog();

		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				urlJsonObj, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json object response
							// response will be a json object
							String name = response.getString("name");
							String email = response.getString("email");
							JSONObject phone = response.getJSONObject("phone");
							String home = phone.getString("home");
							String mobile = phone.getString("mobile");

							jsonResponse = "";
							jsonResponse += "Name: " + name + "\n\n";
							jsonResponse += "Email: " + email + "\n\n";
							jsonResponse += "Home: " + home + "\n\n";
							jsonResponse += "Mobile: " + mobile + "\n\n";

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}


Making JSON Array Request

9. Volley provides JsonArrayRequest class to make json array request. Add the below code in makeJsonArrayRequest() method.

Sample JSON Array Response

URL: http://api.androidhive.info/volley/person_array.json

[
	{
	"name" : "Ravi Tamada", 
	"email" : "ravi8x@gmail.com",
	"phone" : {
		"home" : "08947 000000",
		"mobile" : "9999999999"
	}
	},
	{
	"name" : "Tommy", 
	"email" : "tommy@gmail.com",
	"phone" : {
		"home" : "08946 000000",
		"mobile" : "0000000000"
	}
	}
]
	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {

		showpDialog();

		JsonArrayRequest req = new JsonArrayRequest(urlJsonArry,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json array response
							// loop through each json object
							jsonResponse = "";
							for (int i = 0; i < response.length(); i++) {

								JSONObject person = (JSONObject) response
										.get(i);

								String name = person.getString("name");
								String email = person.getString("email");
								JSONObject phone = person
										.getJSONObject("phone");
								String home = phone.getString("home");
								String mobile = phone.getString("mobile");

								jsonResponse += "Name: " + name + "\n\n";
								jsonResponse += "Email: " + email + "\n\n";
								jsonResponse += "Home: " + home + "\n\n";
								jsonResponse += "Mobile: " + mobile + "\n\n\n";

							}

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						hidepDialog();
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(req);
	}

Now run the project and test it once. You should able to see the parsed json displayed on the screen upon tapping the json request buttons.

android json parsing using volley


Complete Code

Here is the complete code of MainActivity.java

package info.androidhive.volleyjson;

import info.androidhive.volleyjson.R;
import info.androidhive.volleyjson.app.AppController;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {

	// json object response url
	private String urlJsonObj = "http://api.androidhive.info/volley/person_object.json";
	
	// json array response url
	private String urlJsonArry = "http://api.androidhive.info/volley/person_array.json";

	private static String TAG = MainActivity.class.getSimpleName();
	private Button btnMakeObjectRequest, btnMakeArrayRequest;

	// Progress dialog
	private ProgressDialog pDialog;

	private TextView txtResponse;

	// temporary string to show the parsed response
	private String jsonResponse;

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

		btnMakeObjectRequest = (Button) findViewById(R.id.btnObjRequest);
		btnMakeArrayRequest = (Button) findViewById(R.id.btnArrayRequest);
		txtResponse = (TextView) findViewById(R.id.txtResponse);

		pDialog = new ProgressDialog(this);
		pDialog.setMessage("Please wait...");
		pDialog.setCancelable(false);

		btnMakeObjectRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json object request
				makeJsonObjectRequest();
			}
		});

		btnMakeArrayRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json array request
				makeJsonArrayRequest();
			}
		});

	}

	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {

		showpDialog();

		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				urlJsonObj, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json object response
							// response will be a json object
							String name = response.getString("name");
							String email = response.getString("email");
							JSONObject phone = response.getJSONObject("phone");
							String home = phone.getString("home");
							String mobile = phone.getString("mobile");

							jsonResponse = "";
							jsonResponse += "Name: " + name + "\n\n";
							jsonResponse += "Email: " + email + "\n\n";
							jsonResponse += "Home: " + home + "\n\n";
							jsonResponse += "Mobile: " + mobile + "\n\n";

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}

	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {

		showpDialog();

		JsonArrayRequest req = new JsonArrayRequest(urlJsonArry,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json array response
							// loop through each json object
							jsonResponse = "";
							for (int i = 0; i < response.length(); i++) {

								JSONObject person = (JSONObject) response
										.get(i);

								String name = person.getString("name");
								String email = person.getString("email");
								JSONObject phone = person
										.getJSONObject("phone");
								String home = phone.getString("home");
								String mobile = phone.getString("mobile");

								jsonResponse += "Name: " + name + "\n\n";
								jsonResponse += "Email: " + email + "\n\n";
								jsonResponse += "Home: " + home + "\n\n";
								jsonResponse += "Mobile: " + mobile + "\n\n\n";

							}

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						hidepDialog();
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(req);
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}
}
]]>
http://www.androidhive.info/2014/09/android-json-parsing-using-volley/feed/ 0
Android Building Free Wallpapers App – Part 2http://www.androidhive.info/2014/08/android-building-free-wallpapers-app-part-2/ http://www.androidhive.info/2014/08/android-building-free-wallpapers-app-part-2/#comments Wed, 20 Aug 2014 06:18:05 +0000 http://www.androidhive.info/?p=20755 In the 1st Part we prepared required classes for this app. In this we’ll start adding one by one screen to the app and at the end of the article we should be able to complete the application.

android-free-wallpapers-app-banner-2


Styling The Action Bar

12. You might noticed that this app action bar is having customized background color. That can be done by defining a custom theme in res ⇒ values ⇒ styles.xml folder. Also this is not a complete action bar customization. I am just changing the background color of it.

If you want to customize it completely, Styling the Action Bar will helps you. Add below code in res ⇒ values ⇒ styles.xml. If you don’t find styles.xml, create a new file in values folder.

<resources>

    <style name="FreeWallTheme" parent="@android:style/Theme.Holo.Light">
        <item name="android:actionBarStyle">@style/MyActionBarTheme</item>
        <item name="android:actionOverflowButtonStyle">@style/OverFlow</item>
    </style>

    <style name="MyActionBarTheme" parent="@android:style/Widget.Holo.Light.ActionBar">
        <item name="android:background">@color/action_bar</item>
        <item name="android:titleTextStyle">@style/TitleTextStyle</item>
    </style>

    <style name="TitleTextStyle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
        <item name="android:textColor">@color/action_bar_title</item>
    </style>

    <style name="OverFlow" parent="android:style/Widget.Holo.ActionButton.Overflow">
        <item name="android:src">@drawable/ic_action_overflow</item>
    </style>

</resources>



13. Now in order to apply this style, open the AndroidManifest.xml file and add the style using android:theme attribute for tag. Also add below permissions too.

INTERNET – To consume internet
WRITE_EXTERNAL_STORAGE – To store wallpapers in SDCard
SET_WALLPAPER – To update the device wallpaper

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

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.SET_WALLPAPER" />

    <application
        android:name="info.androidhive.awesomewallpapers.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/FreeWallTheme" >
        <!-- all the activities goes here -->
    </application>

</manifest>

Now if you run the app, you should see the action bar color changed.

Adding the Splash Screen

Splash screens are not necessary for every app. But for our app it make sense to have a splash screen as we have to download wallpapers categories before going into the app. In brief we make a request to Picasa to get list of public albums (in our case those are wallpapers categories) and store them in shared preferences. So let’s create the necessary files for the splash screen.

14. Create an xml file named activity_splash.xml under res ⇒ layout folder.

<?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" >

    <ImageView android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:src="@drawable/splash_screen"
        android:scaleType="centerCrop"/>
    

</LinearLayout>
android freewallpapers app splash screen



15. Now create a class named SplashActivity.java in the app main package and paste the below code. Here we are making a json call to Picasa and fetching list of albums. Once albums are fetched we are storing them in Shared Preferences. Later these categories will be loaded into navigation drawer.

package info.androidhive.awesomewallpapers;

import info.androidhive.awesomewallpapers.app.AppConst;
import info.androidhive.awesomewallpapers.app.AppController;
import info.androidhive.awesomewallpapers.picasa.model.Category;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;

public class SplashActivity extends Activity {
	private static final String TAG = SplashActivity.class.getSimpleName();
	private static final String TAG_FEED = "feed", TAG_ENTRY = "entry",
			TAG_GPHOTO_ID = "gphoto$id", TAG_T = "$t",
			TAG_ALBUM_TITLE = "title";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
		getActionBar().hide();
		setContentView(R.layout.activity_splash);

		// Picasa request to get list of albums
		String url = AppConst.URL_PICASA_ALBUMS
				.replace("_PICASA_USER_", AppController.getInstance()
						.getPrefManger().getGoogleUserName());
		
		Log.d(TAG, "Albums request url: " + url);

		// Preparing volley's json object request
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET, url,
				null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, "Albums Response: " + response.toString());
						List<Category> albums = new ArrayList<Category>();
						try {
							// Parsing the json response
							JSONArray entry = response.getJSONObject(TAG_FEED)
									.getJSONArray(TAG_ENTRY);

							// loop through albums nodes and add them to album
							// list
							for (int i = 0; i < entry.length(); i++) {
								JSONObject albumObj = (JSONObject) entry.get(i);
								// album id
								String albumId = albumObj.getJSONObject(
										TAG_GPHOTO_ID).getString(TAG_T);

								// album title
								String albumTitle = albumObj.getJSONObject(
										TAG_ALBUM_TITLE).getString(TAG_T);

								Category album = new Category();
								album.setId(albumId);
								album.setTitle(albumTitle);

								// add album to list
								albums.add(album);

								Log.d(TAG, "Album Id: " + albumId
										+ ", Album Title: " + albumTitle);
							}

							// Store albums in shared pref
							AppController.getInstance().getPrefManger()
									.storeCategories(albums);

							// String the main activity
							Intent intent = new Intent(getApplicationContext(),
									MainActivity.class);
							startActivity(intent);
							// closing spalsh activity
							finish();

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									getString(R.string.msg_unknown_error),
									Toast.LENGTH_LONG).show();
						}

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						Log.e(TAG, "Volley Error: " + error.getMessage());

						// show error toast
						Toast.makeText(getApplicationContext(),
								getString(R.string.splash_error),
								Toast.LENGTH_LONG).show();

						// Unable to fetch albums
						// check for existing Albums data in Shared Preferences
						if (AppController.getInstance().getPrefManger()
								.getCategories() != null && AppController.getInstance().getPrefManger()
								.getCategories().size() > 0) {
							// String the main activity
							Intent intent = new Intent(getApplicationContext(),
									MainActivity.class);
							startActivity(intent);
							// closing spalsh activity
							finish();
						} else {
							// Albums data not present in the shared preferences
							// Launch settings activity, so that user can modify
							// the settings

							Intent i = new Intent(SplashActivity.this,
									SettingsActivity.class);
							// clear all the activities
							i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
									| Intent.FLAG_ACTIVITY_CLEAR_TASK);
							startActivity(i);
						}

					}
				});

		// disable the cache for this request, so that it always fetches updated
		// json
		jsonObjReq.setShouldCache(false);

		// Making the request
		AppController.getInstance().addToRequestQueue(jsonObjReq);

	}

}

16. Now add the SplashActivity as launcher activity in the AndroidManifest.xml inside <application>

   <application ....>
        <activity
            android:name="info.androidhive.awesomewallpapers.SplashActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

Now if you run the app, you should see albums json logged in LogCat. If yes, we can move to the next step i.e adding navigation drawer and displaying the wallpaper categories.

Adding Navigation Drawer

Before following this step make sure that you read Android Sliding Menu using Navigation Drawer tutorial to make this step simple for you. To add navigation drawer, we need to create fewer layouts and classes.

17. Create drawer_list_item.xml under res ⇒ layout directory. This is the layout file for navigation drawer list item to display wallpaper category name.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp" 
    android:background="@drawable/list_selector">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"        
        android:minHeight="?android:attr/listPreferredItemHeightSmall"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"
        android:textColor="@color/list_item_title"
        android:gravity="center_vertical"
        android:paddingRight="10dp"
        android:paddingLeft="10dp"/>

</RelativeLayout>



18. Create NavDrawerItem.java in your project’s main package and paste the below code. This is a model class for navigation list item.



19. Now we need to create the custom list adapter class which provides data to navigation list view. Create a class named NavDrawerListAdapter.java under adapter package. This adapter class inflates the drawer_list_item.xml layout by displaying appropriate wallpaper category name.



20. Now we need to create a list view element in our main activity. Create an xml named activity_main.xml with the below content.

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Framelayout to display Fragments -->
    <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- Listview to display slider menu -->
    <ListView
        android:id="@+id/list_slidermenu"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@color/list_divider"
        android:dividerHeight="1dp"        
        android:listSelector="@drawable/list_selector"
        android:background="@color/list_background"/>
</android.support.v4.widget.DrawerLayout>



21. Open your main activity class (in my case MainActivity.java) and add the below code. Eclipse might show you errors on the lines where you see GridFragment. For now comment those lines.

package info.androidhive.awesomewallpapers;

import info.androidhive.awesomewallpapers.app.AppController;
import info.androidhive.awesomewallpapers.helper.NavDrawerListAdapter;
import info.androidhive.awesomewallpapers.picasa.model.Category;

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

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

public class MainActivity extends Activity {
	private static final String TAG = MainActivity.class.getSimpleName();
	private DrawerLayout mDrawerLayout;
	private ListView mDrawerList;
	private ActionBarDrawerToggle mDrawerToggle;

	// Navigation drawer title
	private CharSequence mDrawerTitle;
	private CharSequence mTitle;
	private List<Category> albumsList;
	private ArrayList<NavDrawerItem> navDrawerItems;
	private NavDrawerListAdapter adapter;

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

		mTitle = mDrawerTitle = getTitle();

		mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
		mDrawerList = (ListView) findViewById(R.id.list_slidermenu);

		navDrawerItems = new ArrayList<NavDrawerItem>();

		// Getting the albums from shared preferences
		albumsList = AppController.getInstance().getPrefManger().getCategories();

		// Insert "Recently Added" in navigation drawer first position
		Category recentAlbum = new Category(null,
				getString(R.string.nav_drawer_recently_added));

		albumsList.add(0, recentAlbum);

		// Loop through albums in add them to navigation drawer adapter
		for (Category a : albumsList) {
			navDrawerItems.add(new NavDrawerItem(a.getId(), a.getTitle()));
		}

		mDrawerList.setOnItemClickListener(new SlideMenuClickListener());

		// Setting the nav drawer list adapter
		adapter = new NavDrawerListAdapter(getApplicationContext(),
				navDrawerItems);
		mDrawerList.setAdapter(adapter);

		// Enabling action bar app icon and behaving it as toggle button
		getActionBar().setDisplayHomeAsUpEnabled(true);
		getActionBar().setHomeButtonEnabled(true);
		getActionBar().setIcon(
				new ColorDrawable(getResources().getColor(
						android.R.color.transparent)));

		mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
				R.drawable.ic_drawer, R.string.app_name, R.string.app_name) {
			public void onDrawerClosed(View view) {
				getActionBar().setTitle(mTitle);
				// calling onPrepareOptionsMenu() to show action bar icons
				invalidateOptionsMenu();
			}

			public void onDrawerOpened(View drawerView) {
				getActionBar().setTitle(mDrawerTitle);
				// calling onPrepareOptionsMenu() to hide action bar icons
				invalidateOptionsMenu();
			}
		};
		mDrawerLayout.setDrawerListener(mDrawerToggle);

		if (savedInstanceState == null) {
			// on first time display view for first nav item
			displayView(0);
		}
	}

	/**
	 * Navigation drawer menu item click listener
	 * */
	private class SlideMenuClickListener implements
			ListView.OnItemClickListener {
		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position,
				long id) {
			// display view for selected nav drawer item
			displayView(position);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	/**
	 * On menu item selected
	 * */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// toggle nav drawer on selecting action bar app icon/title
		if (mDrawerToggle.onOptionsItemSelected(item)) {
			return true;
		}
		// Handle action bar actions click
		switch (item.getItemId()) {
		case R.id.action_settings:
			// Selected settings menu item
			// launch Settings activity
			Intent intent = new Intent(MainActivity.this,
					SettingsActivity.class);
			startActivity(intent);
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	/**
	 * Called when invalidateOptionsMenu() is triggered
	 */
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		// if nav drawer is opened, hide the action items
		boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
		menu.findItem(R.id.action_settings).setVisible(!drawerOpen);
		return super.onPrepareOptionsMenu(menu);
	}

	/**
	 * Diplaying fragment view for selected nav drawer list item
	 * */
	private void displayView(int position) {
		// update the main content by replacing fragments
		Fragment fragment = null;
		switch (position) {
		case 0:
			// Recently added item selected
			// don't pass album id to grid fragment
			fragment = GridFragment.newInstance(null);
			break;

		default:
			// selected wallpaper category
			// send album id to grid fragment to list all the wallpapers
			String albumId = albumsList.get(position).getId();
			fragment = GridFragment.newInstance(albumId);
			break;
		}

		if (fragment != null) {
			FragmentManager fragmentManager = getFragmentManager();
			fragmentManager.beginTransaction()
					.replace(R.id.frame_container, fragment).commit();

			// update selected item and title, then close the drawer
			mDrawerList.setItemChecked(position, true);
			mDrawerList.setSelection(position);
			setTitle(albumsList.get(position).getTitle());
			mDrawerLayout.closeDrawer(mDrawerList);
		} else {
			// error in creating fragment
			Log.e(TAG, "Error in creating fragment");
		}
	}

	@Override
	public void setTitle(CharSequence title) {
		mTitle = title;
		getActionBar().setTitle(mTitle);
	}

	/**
	 * When using the ActionBarDrawerToggle, you must call it during
	 * onPostCreate() and onConfigurationChanged()...
	 */

	@Override
	protected void onPostCreate(Bundle savedInstanceState) {
		super.onPostCreate(savedInstanceState);
		// Sync the toggle state after onRestoreInstanceState has occurred.
		mDrawerToggle.syncState();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		// Pass any configuration change to the drawer toggls
		mDrawerToggle.onConfigurationChanged(newConfig);
	}
}

22. Add the MainActivity.java activity in the AndroidManifest.xml inside <application> tag.

<application ...>
        <activity
            android:name="info.androidhive.awesomewallpapers.MainActivity"
            android:screenOrientation="portrait" >
        </activity>
</application>

Now run the app and test it once. You should see the wallpaper categories displayed in navigation drawer.

android free wallpapers app navigation drawer


Wallpapers Grid Thumbnail Preview

The next step is displaying the wallpapers of selected category in a thumbnail fashion. For this we need a GridView and few other class files.

23. Create an xml file named grid_item_photo.xml under res ⇒ layout and add the below code. This layout defines single grid item tile in the GridView. Here we are using volley’s NetworkImageView to download the image from an URL and display.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/grid_item_bg" >

    <ImageView
        android:id="@+id/imgLoader"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerInParent="true"
        android:src="@drawable/ico_loader" />

    <!-- Thumbnail Image -->

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>



24. Create another layout file named fragment_grid.xml under res ⇒ layout folder. This layout contains actual GridView and a ProgressBar to show to loading progress while the request is in process.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:background="@color/black">

    <ProgressBar
        android:id="@+id/pbLoader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" 
        style="?android:attr/progressBarStyle">
    </ProgressBar>

    <GridView
        android:id="@+id/grid_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@color/grid_bg"
        android:gravity="center"
        android:numColumns="auto_fit"
        android:stretchMode="columnWidth">
    </GridView>

</RelativeLayout>



25. Now just like navigation drawer adapter, we need to create another adapter class to provide data to GridView. So create a class named GridViewAdapter.java under adapter package. This class inflates grid_item_photo.xml layout with appropriate wallpaper image.

package info.androidhive.awesomewallpapers.helper;

import info.androidhive.awesomewallpapers.R;
import info.androidhive.awesomewallpapers.app.AppController;
import info.androidhive.awesomewallpapers.picasa.model.Wallpaper;

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

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class GridViewAdapter extends BaseAdapter {

	private Activity _activity;
	private LayoutInflater inflater;
	private List<Wallpaper> wallpapersList = new ArrayList<Wallpaper>();
	private int imageWidth;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public GridViewAdapter(Activity activity, List<Wallpaper> wallpapersList,
			int imageWidth) {
		this._activity = activity;
		this.wallpapersList = wallpapersList;
		this.imageWidth = imageWidth;
	}

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

	@Override
	public Object getItem(int position) {
		return this.wallpapersList.get(position);
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if (inflater == null)
			inflater = (LayoutInflater) _activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.grid_item_photo, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();

		// Grid thumbnail image view
		NetworkImageView thumbNail = (NetworkImageView) convertView
				.findViewById(R.id.thumbnail);

		Wallpaper p = wallpapersList.get(position);

		thumbNail.setScaleType(ImageView.ScaleType.CENTER_CROP);
		thumbNail.setLayoutParams(new RelativeLayout.LayoutParams(imageWidth,
				imageWidth));
		thumbNail.setImageUrl(p.getUrl(), imageLoader);

		return convertView;
	}

}



26. Create a class named GridFragment.java under the project’s main package. This is where we implement actual GridView implementation. This is main fragment class will be loaded when an item from navigation drawer selected. Briefly it works as below

> User selects a wallpaper category from navigation drawer.
> The selected category id (in other words album id) will be passed to GridFragment
> Using the category id, we make another request to Picasa to get all the photos under that category.
> Finally the photos json will be parsed and wallpaper model objects will be passed to GridViewAdpater to render the wallpapers in grid view.

package info.androidhive.awesomewallpapers;

import info.androidhive.awesomewallpapers.app.AppConst;
import info.androidhive.awesomewallpapers.app.AppController;
import info.androidhive.awesomewallpapers.helper.GridViewAdapter;
import info.androidhive.awesomewallpapers.picasa.model.Wallpaper;
import info.androidhive.awesomewallpapers.util.PrefManager;
import info.androidhive.awesomewallpapers.util.Utils;

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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Fragment;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;

public class GridFragment extends Fragment {
	private static final String TAG = GridFragment.class.getSimpleName();
	private Utils utils;
	private GridViewAdapter adapter;
	private GridView gridView;
	private int columnWidth;
	private static final String bundleAlbumId = "albumId";
	private String selectedAlbumId;
	private List<Wallpaper> photosList;
	private ProgressBar pbLoader;
	private PrefManager pref;

	// Picasa JSON response node keys
	private static final String TAG_FEED = "feed", TAG_ENTRY = "entry",
			TAG_MEDIA_GROUP = "media$group",
			TAG_MEDIA_CONTENT = "media$content", TAG_IMG_URL = "url",
			TAG_IMG_WIDTH = "width", TAG_IMG_HEIGHT = "height", TAG_ID = "id",
			TAG_T = "$t";

	public GridFragment() {
	}

	public static GridFragment newInstance(String albumId) {
		GridFragment f = new GridFragment();
		Bundle args = new Bundle();
		args.putString(bundleAlbumId, albumId);
		f.setArguments(args);
		return f;
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		photosList = new ArrayList<Wallpaper>();
		pref = new PrefManager(getActivity());

		// Getting Album Id of the item selected in navigation drawer
		// if Album Id is null, user is selected recently added option
		if (getArguments().getString(bundleAlbumId) != null) {
			selectedAlbumId = getArguments().getString(bundleAlbumId);
			Log.d(TAG,
					"Selected album id: "
							+ getArguments().getString(bundleAlbumId));
		} else {
			Log.d(TAG, "Selected recentlyed added album");
			selectedAlbumId = null;
		}

		// Preparing the request url
		String url = null;
		if (selectedAlbumId == null) {
			// Recently added album url
			url = AppConst.URL_RECENTLY_ADDED.replace("_PICASA_USER_",
					pref.getGoogleUserName());
		} else {
			// Selected an album, replace the Album Id in the url
			url = AppConst.URL_ALBUM_PHOTOS.replace("_PICASA_USER_",
					pref.getGoogleUserName()).replace("_ALBUM_ID_",
					selectedAlbumId);
		}

		Log.d(TAG, "Final request url: " + url);

		View rootView = inflater.inflate(R.layout.fragment_grid, container,
				false);

		// Hiding the gridview and showing loader image before making the http
		// request
		gridView = (GridView) rootView.findViewById(R.id.grid_view);
		gridView.setVisibility(View.GONE);
		pbLoader = (ProgressBar) rootView.findViewById(R.id.pbLoader);
		pbLoader.setVisibility(View.VISIBLE);

		utils = new Utils(getActivity());

		/**
		 * Making volley's json object request to fetch list of photos of an
		 * album
		 * */
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET, url,
				null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG,
								"List of photos json reponse: "
										+ response.toString());
						try {
							// Parsing the json response
							JSONArray entry = response.getJSONObject(TAG_FEED)
									.getJSONArray(TAG_ENTRY);

							// looping through each photo and adding it to list
							// data set
							for (int i = 0; i < entry.length(); i++) {
								JSONObject photoObj = (JSONObject) entry.get(i);
								JSONArray mediacontentArry = photoObj
										.getJSONObject(TAG_MEDIA_GROUP)
										.getJSONArray(TAG_MEDIA_CONTENT);

								if (mediacontentArry.length() > 0) {
									JSONObject mediaObj = (JSONObject) mediacontentArry
											.get(0);

									String url = mediaObj
											.getString(TAG_IMG_URL);

									String photoJson = photoObj.getJSONObject(
											TAG_ID).getString(TAG_T)
											+ "&imgmax=d";									

									int width = mediaObj.getInt(TAG_IMG_WIDTH);
									int height = mediaObj
											.getInt(TAG_IMG_HEIGHT);

									Wallpaper p = new Wallpaper(photoJson, url, width,
											height);

									// Adding the photo to list data set
									photosList.add(p);

									Log.d(TAG, "Photo: " + url + ", w: "
											+ width + ", h: " + height);
								}
							}

							// Notify list adapter about dataset changes. So
							// that it renders grid again
							adapter.notifyDataSetChanged();

							// Hide the loader, make grid visible
							pbLoader.setVisibility(View.GONE);
							gridView.setVisibility(View.VISIBLE);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getActivity(),
									getString(R.string.msg_unknown_error),
									Toast.LENGTH_LONG).show();
						}

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						Log.e(TAG, "Error: " + error.getMessage());
						// unable to fetch wallpapers
						// either google username is wrong or
						// devices doesn't have internet connection
						Toast.makeText(getActivity(),
								getString(R.string.msg_wall_fetch_error),
								Toast.LENGTH_LONG).show();

					}
				});

		// Remove the url from cache
		AppController.getInstance().getRequestQueue().getCache().remove(url);

		// Disable the cache for this url, so that it always fetches updated
		// json
		jsonObjReq.setShouldCache(false);

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);

		// Initilizing Grid View
		InitilizeGridLayout();

		// Gridview adapter
		adapter = new GridViewAdapter(getActivity(), photosList, columnWidth);

		// setting grid view adapter
		gridView.setAdapter(adapter);

		// Grid item select listener
		gridView.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View v,
					int position, long id) {

				// On selecting the grid image, we launch fullscreen activity
				Intent i = new Intent(getActivity(),
						FullScreenViewActivity.class);

				// Passing selected image to fullscreen activity
				Wallpaper photo = photosList.get(position);

				i.putExtra(FullScreenViewActivity.TAG_SEL_IMAGE, photo);
				startActivity(i);
			}
		});

		return rootView;
	}

	/**
	 * Method to calculate the grid dimensions Calculates number columns and
	 * columns width in grid
	 * */
	private void InitilizeGridLayout() {
		Resources r = getResources();
		float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
				AppConst.GRID_PADDING, r.getDisplayMetrics());

		// Column width
		columnWidth = (int) ((utils.getScreenWidth() - ((pref
				.getNoOfGridColumns() + 1) * padding)) / pref
				.getNoOfGridColumns());

		// Setting number of grid columns
		gridView.setNumColumns(pref.getNoOfGridColumns());
		gridView.setColumnWidth(columnWidth);
		gridView.setStretchMode(GridView.NO_STRETCH);
		gridView.setPadding((int) padding, (int) padding, (int) padding,
				(int) padding);

		// Setting horizontal and vertical padding
		gridView.setHorizontalSpacing((int) padding);
		gridView.setVerticalSpacing((int) padding);
	}

}

Now go back to your MainActivity.java, uncomment the GridFragment related code which you commented earlier and run the project. You should able to see the wallpapers in GridView once you selected the category from navigation drawer.

android free wallpapers app grid view


Wallpaper Fullscreen View

We also providing user another option to view selected thumbnail image in fullscreen mode. So to implement this, follow below steps.

27. Create an xml file named activity_fullscreen_image.xml under res ⇒ layout folder. This file contains HorizontalScrollView element to scroll the fullscreen image horizontally. Also it contains two more buttons to provide user option to apply the image as wallpaper and download the wallpaper to galleries.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black" >

    <ProgressBar
        android:id="@+id/pbLoader"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" >
    </ProgressBar>

    <!-- Scroll view for fullscreen preview -->

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent" >

            <ImageView
                android:id="@+id/imgFullscreen"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="fitXY" />
        </LinearLayout>
    </HorizontalScrollView>

    <!-- Set as wallpaper button -->

    <LinearLayout
        android:id="@+id/llSetWallpaper"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:background="@drawable/btn_rounded_corner"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:src="@drawable/ico_apply" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="10dp"
            android:text="@string/set_wallpaper"
            android:textColor="@color/white"
            android:textSize="18dp" />
    </LinearLayout>

    <!-- Download wallpaper button -->

    <LinearLayout
        android:id="@+id/llDownloadWallpaper"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="10dp"
        android:layout_marginRight="10dp"
        android:background="@drawable/btn_rounded_corner"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:src="@drawable/ico_download" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="10dp"
            android:text="@string/download_wallpaper"
            android:textColor="@color/white"
            android:textSize="18dp" />
    </LinearLayout>

</RelativeLayout>



28. Create a class named FullScreenViewActivity.java under your main package. This class takes care of calculating the image aspect ratio in fullscreen mode. The image width will calculated respective to device height. Below steps will be execute in order show the fullscreen wallpaper.

> User selects the wallpaper from GridView. The selected wallpaper will be passed to fullscreen image activity.
> In fullscreen activity, we make another call to Picasa to get the selected wallpaper’s high resolution version.
> The downloaded wallpaper width will be calculated depending upon device height.
> Then the image will be displayed in ImageView. The HorizontalScrollView make the wallpaper scrollable horizontally.

package info.androidhive.awesomewallpapers;

import info.androidhive.awesomewallpapers.app.AppController;
import info.androidhive.awesomewallpapers.picasa.model.Wallpaper;
import info.androidhive.awesomewallpapers.util.Utils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;
import com.android.volley.toolbox.JsonObjectRequest;

public class FullScreenViewActivity extends Activity implements OnClickListener {
	private static final String TAG = FullScreenViewActivity.class
			.getSimpleName();
	public static final String TAG_SEL_IMAGE = "selectedImage";
	private Wallpaper selectedPhoto;
	private ImageView fullImageView;
	private LinearLayout llSetWallpaper, llDownloadWallpaper;
	private Utils utils;
	private ProgressBar pbLoader;

	// Picasa JSON response node keys
	private static final String TAG_ENTRY = "entry",
			TAG_MEDIA_GROUP = "media$group",
			TAG_MEDIA_CONTENT = "media$content", TAG_IMG_URL = "url",
			TAG_IMG_WIDTH = "width", TAG_IMG_HEIGHT = "height";

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

		fullImageView = (ImageView) findViewById(R.id.imgFullscreen);
		llSetWallpaper = (LinearLayout) findViewById(R.id.llSetWallpaper);
		llDownloadWallpaper = (LinearLayout) findViewById(R.id.llDownloadWallpaper);
		pbLoader = (ProgressBar) findViewById(R.id.pbLoader);

		// hide the action bar in fullscreen mode
		getActionBar().hide();

		utils = new Utils(getApplicationContext());

		// layout click listeners
		llSetWallpaper.setOnClickListener(this);
		llDownloadWallpaper.setOnClickListener(this);

		// setting layout buttons alpha/opacity
		llSetWallpaper.getBackground().setAlpha(70);
		llDownloadWallpaper.getBackground().setAlpha(70);

		Intent i = getIntent();
		selectedPhoto = (Wallpaper) i.getSerializableExtra(TAG_SEL_IMAGE);

		// check for selected photo null
		if (selectedPhoto != null) {

			// fetch photo full resolution image by making another json request
			fetchFullResolutionImage();

		} else {
			Toast.makeText(getApplicationContext(),
					getString(R.string.msg_unknown_error), Toast.LENGTH_SHORT)
					.show();
		}
	}

	/**
	 * Fetching image fullresolution json
	 * */
	private void fetchFullResolutionImage() {
		String url = selectedPhoto.getPhotoJson();

		// show loader before making request
		pbLoader.setVisibility(View.VISIBLE);
		llSetWallpaper.setVisibility(View.GONE);
		llDownloadWallpaper.setVisibility(View.GONE);

		// volley's json obj request
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET, url,
				null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG,
								"Image full resolution json: "
										+ response.toString());
						try {
							// Parsing the json response
							JSONObject entry = response
									.getJSONObject(TAG_ENTRY);

							JSONArray mediacontentArry = entry.getJSONObject(
									TAG_MEDIA_GROUP).getJSONArray(
									TAG_MEDIA_CONTENT);

							JSONObject mediaObj = (JSONObject) mediacontentArry
									.get(0);

							String fullResolutionUrl = mediaObj
									.getString(TAG_IMG_URL);

							// image full resolution widht and height
							final int width = mediaObj.getInt(TAG_IMG_WIDTH);
							final int height = mediaObj.getInt(TAG_IMG_HEIGHT);

							Log.d(TAG, "Full resolution image. url: "
									+ fullResolutionUrl + ", w: " + width
									+ ", h: " + height);

							ImageLoader imageLoader = AppController
									.getInstance().getImageLoader();

							// We download image into ImageView instead of
							// NetworkImageView to have callback methods
							// Currently NetworkImageView doesn't have callback
							// methods

							imageLoader.get(fullResolutionUrl,
									new ImageListener() {

										@Override
										public void onErrorResponse(
												VolleyError arg0) {
											Toast.makeText(
													getApplicationContext(),
													getString(R.string.msg_wall_fetch_error),
													Toast.LENGTH_LONG).show();
										}

										@Override
										public void onResponse(
												ImageContainer response,
												boolean arg1) {
											if (response.getBitmap() != null) {
												// load bitmap into imageview
												fullImageView
														.setImageBitmap(response
																.getBitmap());
												adjustImageAspect(width, height);

												// hide loader and show set &
												// download buttons
												pbLoader.setVisibility(View.GONE);
												llSetWallpaper
														.setVisibility(View.VISIBLE);
												llDownloadWallpaper
														.setVisibility(View.VISIBLE);
											}
										}
									});

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									getString(R.string.msg_unknown_error),
									Toast.LENGTH_LONG).show();
						}

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						Log.e(TAG, "Error: " + error.getMessage());
						// unable to fetch wallpapers
						// either google username is wrong or
						// devices doesn't have internet connection
						Toast.makeText(getApplicationContext(),
								getString(R.string.msg_wall_fetch_error),
								Toast.LENGTH_LONG).show();

					}
				});

		// Remove the url from cache
		AppController.getInstance().getRequestQueue().getCache().remove(url);

		// Disable the cache for this url, so that it always fetches updated
		// json
		jsonObjReq.setShouldCache(false);

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}

	/**
	 * Adjusting the image aspect ration to scroll horizontally, Image height
	 * will be screen height, width will be calculated respected to height
	 * */
	@SuppressWarnings("deprecation")
	@SuppressLint("NewApi")
	private void adjustImageAspect(int bWidth, int bHeight) {
		LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

		if (bWidth == 0 || bHeight == 0)
			return;

		int sHeight = 0;

		if (android.os.Build.VERSION.SDK_INT >= 13) {
			Display display = getWindowManager().getDefaultDisplay();
			Point size = new Point();
			display.getSize(size);
			sHeight = size.y;
		} else {
			Display display = getWindowManager().getDefaultDisplay();
			sHeight = display.getHeight();
		}

		int new_width = (int) Math.floor((double) bWidth * (double) sHeight
				/ (double) bHeight);
		params.width = new_width;
		params.height = sHeight;

		Log.d(TAG, "Fullscreen image new dimensions: w = " + new_width
				+ ", h = " + sHeight);

		fullImageView.setLayoutParams(params);
	}

	/**
	 * View click listener
	 * */
	@Override
	public void onClick(View v) {
		Bitmap bitmap = ((BitmapDrawable) fullImageView.getDrawable())
				.getBitmap();
		switch (v.getId()) {
		// button Download Wallpaper tapped
		case R.id.llDownloadWallpaper:
			utils.saveImageToSDCard(bitmap);
			break;
		// button Set As Wallpaper tapped
		case R.id.llSetWallpaper:
			utils.setAsWallpaper(bitmap);
			break;
		default:
			break;
		}

	}
}



29. Add the FullScreenViewActivity entry in AndroidManifest.xml file between <application> tags.

<application ...>       
        <activity
            android:name="info.androidhive.awesomewallpapers.FullScreenViewActivity"
            android:screenOrientation="portrait" >
        </activity>
        </activity>
</application>

Now run the project and select any image from grid view. You should able see fullscreen image activity launched with wallpaper image preview.

android free wallpapers app grid view


Providing Set as Wallpaper Option

30. To apply the wallpaper to device, add a click listener to set as wallpaper button and call setAsWallpaper() method presented in Utils.java class

Add below line in onCreate() method of FullScreenViewActivity.java

llSetWallpaper.setOnClickListener(this);

Call below method in onClick() method of FullScreenViewActivity.java

	/**
	 * View click listener
	 * */
	@Override
	public void onClick(View v) {
		Bitmap bitmap = ((BitmapDrawable) fullImageView.getDrawable())
				.getBitmap();
		switch (v.getId()) {		
		case R.id.llSetWallpaper:
			utils.setAsWallpaper(bitmap);
			break;
		default:
			break;
		}

	}

Now if you tap the Set button, the wallpaper will be applied to device.

android free wallpapers set as wallpaper


Downloading Wallpaper to Gallery

31. Just like set as wallpaper, to download wallpaper to device gallery directory, add the click listener to download button and call saveImageToSDCard() method presented in Utils.java

Add below line in onCreate() method of FullScreenViewActivity.java

llDownloadWallpaper.setOnClickListener(this);

Add another case statement in onClick() method of FullScreenViewActivity.java

	/**
	 * View click listener
	 * */
	@Override
	public void onClick(View v) {
		Bitmap bitmap = ((BitmapDrawable) fullImageView.getDrawable())
				.getBitmap();
		switch (v.getId()) {
		// button Download Wallpaper tapped
		case R.id.llDownloadWallpaper:
			utils.saveImageToSDCard(bitmap);
			break;
		// button Set As Wallpaper tapped
		case R.id.llSetWallpaper:
			utils.setAsWallpaper(bitmap);
			break;
		default:
			break;
		}

	}

Now if you tap Download Wallpaper button, the wallpaper will be downloaded to your gallery with album named Awesome Wallpapers. If you don’t see the album there, reboot your device. I couldn’t see the album created until I rebooted my device. I am yet to find the issue :(


With this we almost the completed the app. But lastly we need to add a Settings screen where user can configure Picasa username, number of grid columns and gallery directory name. This step is not necessary if you are planning to release this app in playstore. It is just for the developers who is reading this.

Adding Settings Screen

32. . Create a layout file named activity_settings.xml under res layout folder. This layout contains a form with few edittext fields and a save button.

<?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"
    android:padding="10dp" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="15dp"
        android:text="@string/lbl_google_username"
        android:textColor="@color/settings_label"
        android:textSize="16dp" />

    <EditText
        android:id="@+id/txtGoogleUsername"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:background="@drawable/edittext_rounded_corner"
        android:inputType="text"
        android:singleLine="true" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="@string/lbl_no_grid_columns"
        android:textColor="@color/settings_label"
        android:textSize="16dp" />

    <EditText
        android:id="@+id/txtNoOfColumns"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:background="@drawable/edittext_rounded_corner"
        android:inputType="number"
        android:singleLine="true" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="@string/lbl_gallery_name"
        android:textColor="@color/settings_label"
        android:textSize="16dp" />

    <EditText
        android:id="@+id/txtGalleryName"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"
        android:background="@drawable/edittext_rounded_corner"
        android:singleLine="true" />

    <Button
        android:id="@+id/btnSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/btn_rounded_corner"
        android:paddingLeft="30dp"
        android:paddingRight="30dp"
        android:text="@string/lbl_btn_save"
        android:textColor="@color/white"
        android:textSize="18dp" />

</LinearLayout>



33. . Create a class named SettingsActivity.java and add the below code. This code just validates the data entered by user and overwrites the respective values in Shared Preferences.

package info.androidhive.awesomewallpapers;

import info.androidhive.awesomewallpapers.R;
import info.androidhive.awesomewallpapers.util.PrefManager;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class SettingsActivity extends Activity {
	private PrefManager pref;
	private TextView txtGoogleUsername, txtNoOfGridColumns, txtGalleryName;
	private Button btnSave;

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

		txtGoogleUsername = (TextView) findViewById(R.id.txtGoogleUsername);
		txtNoOfGridColumns = (TextView) findViewById(R.id.txtNoOfColumns);
		txtGalleryName = (TextView) findViewById(R.id.txtGalleryName);
		btnSave = (Button) findViewById(R.id.btnSave);

		pref = new PrefManager(getApplicationContext());

		// Display edittext values stored in shared preferences
		// Google username
		txtGoogleUsername.setText(pref.getGoogleUserName());

		// Number of grid columns
		txtNoOfGridColumns.setText(String.valueOf(pref.getNoOfGridColumns()));

		// Gallery name
		txtGalleryName.setText(pref.getGalleryName());

		// Save settings button click listener
		btnSave.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {

				// Validating the data before saving to shared preferences
				// validate google username
				String googleUsername = txtGoogleUsername.getText().toString()
						.trim();
				if (googleUsername.length() == 0) {
					Toast.makeText(getApplicationContext(),
							getString(R.string.toast_enter_google_username),
							Toast.LENGTH_LONG).show();
					return;
				}

				// validate number of grid columns
				String no_of_columns = txtNoOfGridColumns.getText().toString()
						.trim();
				if (no_of_columns.length() == 0 || !isInteger(no_of_columns)) {
					Toast.makeText(getApplicationContext(),
							getString(R.string.toast_enter_valid_grid_columns),
							Toast.LENGTH_LONG).show();
					return;
				}

				// validate gallery name
				String galleryName = txtGalleryName.getText().toString().trim();
				if (galleryName.length() == 0) {
					Toast.makeText(getApplicationContext(),
							getString(R.string.toast_enter_gallery_name),
							Toast.LENGTH_LONG).show();
					return;
				}

				// Check for setting changes
				if (!googleUsername.equalsIgnoreCase(pref.getGoogleUserName())
						|| !no_of_columns.equalsIgnoreCase(String.valueOf(pref
								.getNoOfGridColumns()))
						|| !galleryName.equalsIgnoreCase(pref.getGalleryName())) {
					// User changed the settings
					// save the changes and launch SplashScreen to initialize
					// the app again
					pref.setGoogleUsername(googleUsername);
					pref.setNoOfGridColumns(Integer.parseInt(no_of_columns));
					pref.setGalleryName(galleryName);

					// start the app from SplashScreen
					Intent i = new Intent(SettingsActivity.this,
							SplashActivity.class);
					// Clear all the previous activities
					i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
							| Intent.FLAG_ACTIVITY_CLEAR_TASK);
					startActivity(i);
				} else {
					// user not modified any values in the form
					// skip saving to shared preferences
					// just go back to previous activity
					onBackPressed();
				}

			}
		});

	}

	public boolean isInteger(String input) {
		try {
			Integer.parseInt(input);
			return true;
		} catch (Exception e) {
			return false;
		}
	}
}

34. Finally add settings activity entry in AndroidManifest.xml

    <application ...>        
        <activity
            android:name="info.androidhive.awesomewallpapers.SettingsActivity"
            android:label="@string/action_settings"
            android:screenOrientation="portrait" >
        </activity>
    </application>

Finally your AndroidManifest.xml file should look like this.

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

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.SET_WALLPAPER" />

    <application
        android:name="info.androidhive.awesomewallpapers.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/FreeWallTheme" >
        <activity
            android:name="info.androidhive.awesomewallpapers.SplashActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="info.androidhive.awesomewallpapers.MainActivity"
            android:screenOrientation="portrait" >
        </activity>
        <activity
            android:name="info.androidhive.awesomewallpapers.FullScreenViewActivity"
            android:screenOrientation="portrait" >
        </activity>
        <activity
            android:name="info.androidhive.awesomewallpapers.SettingsActivity"
            android:label="@string/action_settings"
            android:screenOrientation="portrait" >
        </activity>
    </application>

</manifest>

Run the app and launch settings screen by tapping overflow icon on action bar.


Congratulations :) If you are succeeded all the steps until now, you just create a fully fledged wallpapers app which has good potential go into market right away. But before that I would like to address few important points to you.

> This app is doesn’t supports older versions < 4.0. For that you need to use Android Support Library to provide backward compatibility.

> Even though we are using Google+ / Picasa web services to provide wallpapers, in real scenario we should have a server side technology to detect the mobile device and respond the wallpapers depending on mobile configuration like screen dimensions.

If you have any queries or suggestions please leave in comment section below.

]]>
http://www.androidhive.info/2014/08/android-building-free-wallpapers-app-part-2/feed/ 0