Wednesday, August 27, 2014

Google Cloud Messaging for Android (GCM) with example

Table of Content


             Creating a Google API project
                          Enabling the GCM Service
                          Obtaining an API Key
             Implementing GCM Client
                          Step 1: Setup Google Play Services
                          Step 2 : Application’s manifest configuration
                          Step 3: Write Your Application
                                       MainActivity layout xml
                                       Receive a message
             Implementing a GCM Server
                          Project Setup
                          Writing code to send message to android device
             Sample App Downloads:
                          Android sample app
                          Java server application for sending GCM messages

GCM Concept

Google Cloud Messaging for Android (GCM) is a free service that helps developers send data from servers to their Android applications on Android devices, and upstream messages from the user's device back to the cloud (upstream messages are only supported in XMPP).

The message could be a ligh message telling the Android application that there is new data to be fetched from the server (for instance, a "new email" notification informing the application that it is out of sync with the back end), or it could be a message containing up to 4kb of payload data (so apps like instant messaging can consume the message directly).

Entities involved in GCM

Components : The entities that play a primary role in GCM.
Credentials : The IDs and tokens that are used in different stages of GCM to ensure that all parties have been authenticated, and that the message is going to the correct place.

Components

Client App
The GCM-enabled Android application that is running on a device. This must be a 2.2 Android device that has Google Play Store installed, and it must have at least one logged in Google account if the device is running a version lower than Android 4.0.4. Alternatively, for testing you can use an emulator running Android 2.2 with Google APIs.
3rd-party Application Server
An application server that you write as part of implementing GCM. The 3rd-party application server sends data to an Android application on the device via the GCM connection server.
GCM Connection Servers
The Google-provided servers involved in taking messages from the 3rd-party application server and sending them to the device.
Credentials

Sender ID
A project number you acquire from the API console. The sender ID is used in the registering android device to connection server.
Application ID
The Android application that is registering to receive messages. The Android application is identified by the package name from the manifest. This ensures that the messages are targeted to the correct Android application.
Registration ID
An ID issued by the GCM servers to the Android application that allows it to receive messages. Once the Android application has the registration ID, it sends it to the 3rd-party application server, which uses it to identify each device that has registered to receive messages for a given Android application. In other words, a registration ID is tied to a particular Android application running on a particular device. Note that registration IDs must be kept secret.


Note: If you use backup and restore, you should explicitly avoid backing up registration IDs. When you backup a device, apps backup shared prefs indiscriminately. If you don't explicitly exclude the GCM registration ID, it could get reused on a new device, which would cause delivery errors.
Google User Account
For GCM to work, the mobile device must include at least one Google account if the device is running a version lower than Android 4.0.4.
Sender Auth Token
An API key that is saved on the 3rd-party application server that gives the application server authorized access to Google services. The API key is included in the header of POST requests that send messages.

Implementation:

Before you begin, make sure to set up the Google Play Services SDK. You need this SDK to use the GoogleCloudMessaging methods.

Creating a Google API project

The first thing is to get the sender ID described above. Its the project number of the project created in Google Developer Console.

You have to create a new project there and have to get the project number. This would be your GCM Sender ID to be used in registering android device to GCM.

In the sidebar on the left, select APIs & auth.
In the displayed list of APIs, turn the Google Cloud Messaging for Android toggle to ON.


1) In the sidebar on the left, select APIs & auth > Credentials.
2) Under Public API access, click Create new key.
3) In the Create a new key dialog, click Server key.
4) In the resulting configuration dialog, supply your server's IP address. For testing purposes, you can use 0.0.0.0/0
5) Click Create.
6) In the refreshed page, copy the API key. You will need the API key later on to perform authentication in your application server.

Note: If you need to rotate the key, click Regenerate key. A new key will be created. If you think the key has been compromised and you want to delete it immediately, click Delete.


Setup is done. Now you have to implement below components.

1) GCM Server : Java application which will be hosted on application server. This will send messages to GCM connection servers, which in turn deliver messages to android application running on android devices.
2) GCM Client : Android application
3) Decide which GCM connection server you want to use (HTTP or XMPP)


Implementing GCM Client

A GCM client is a GCM-enabled app that runs on an Android device.

We will implement a below simple android application which will register with gcm connection server and generate registration id to be used by 3rd party application server for sending the message to the same android device.


gcm sample android application

This android app must have below 2 functionalities.

1) It must include a code to register android device to GCM connection server and thereby get a registration ID.
2) It must include a broadcast receiver to receive messages sent by GCM server.

Step 1: Setup Google Play Services

To write your client application, we will use GoogleCloudMessaging API. 
To use this API, you must set up your project to use the Google Play services SDK, as described in Setup Google Play Services SDK setup.

Also if you are using android emulator to test the gcm application, then you have to use emulator for android version >= 4.1.2 . 
and while creating AVD you have to select Google APIs as a target. 
If you don’t have Google APIs not available as a target, then open SDK manager and download the same. Once you make any change in SDK manager (install/remove packages), then you have to restart your eclipse to make those changes available in AVD manager.

1) Following meta tag must be present under application tag. This is required if you are using google play services SDK.

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

2) Add permission : com.google.android.c2dm.permission.RECEIVE


<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />


This is required so the application can register to get registration id and to receive messages sent by GCM server.

3) Add permission : android.permission.INTERNET

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

4) Add permission : android.permission.GET_ACCOUNTS

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

This is to get user account information as GCM requires a Google account (necessary only if if the device is running a version lower than Android 4.0.4).

5) An applicationPackage + ".permission.C2D_MESSAGE" permission to prevent other Android applications from registering and receiving the Android application's messages. The permission name must exactly match this pattern—otherwise the Android application will not receive the messages.

lets consider applicatonPackage as "com.example.gcm" , then the entry would be like : 

<permission android:name="com.example.gcm.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />

6) A broadcast receiver for getting messages sent by 3rd party application server. This should be an instance of WakefulBroadcastReceiver.

The receiver should require the com.google.android.c2dm.SEND permission, so that only the GCM Framework can send a message to it.

<receiver android:name=".GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
 <intent-filter>
         <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.example.gcm" />  <!-- application package name -->
        </intent-filter>
</receiver>

7) A Service (typically an IntentService) to which the WakefulBroadcastReceiver passes off the work of handling the GCM message, while ensuring that the device does not go back to sleep in the process.

You could choose to process your messages in a regular BroadcastReceiver instead, but realistically, most apps will use a IntentService.

<service android:name=".GcmIntentService" />

8) If the GCM feature is critical to the Android application's function, be sure to set android:minSdkVersion="8" or higher in the manifest. This ensures that the Android application cannot be installed in an environment in which it could not run properly.

Step 3: Write Your Application

Finally write your application.
In this section we will only see how to register to GCM connection server and get registration Id using GoogleCloudMessaging APIs. 

This contains one edittext which will hold registration id. and one button onclick of which we will the register application to GCM.

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

    <TextView
        android:id="@+id/lbl_reg_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GCM Registration ID: " />

    <EditText
        android:id="@+id/txt_reg_id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbl_reg_id"/>

    <Button
        android:id="@+id/btn_reg_device"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/txt_reg_id"
        android:text="Get Registration ID"
        android:onClick="onClickRegDevice" />

</RelativeLayout>

MainActivity.java (Google Play Services APK compatibility check on android device and GCM registration)

Following functionalities have been implemented in this.
1) Checking whether android device has google play service APK. If not then displaying appropriate dialog box to download and install it.
2) Registering with GCM to get registration ID to be used by 3rd party application.

package com.example.gcm;

import java.io.IOException;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;

public class MainActivity extends Activity
{
 private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
 public static final String PROPERTY_REG_ID = "registration_id";
 private static final String PROPERTY_APP_VERSION = "appVersion";

 private static String SENDER_ID = "601094561";
 private GoogleCloudMessaging gcm = null;

 private EditText txt_reg_id = null;

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

  txt_reg_id = (EditText) this.findViewById(R.id.txt_reg_id);

  if (checkPlayServices())
  {
  }
  else
  {
   Toast.makeText(this, "This device is not supported.", Toast.LENGTH_LONG).show();
  }
 }

 public void onClickRegDevice(View view)
 {
  System.out.println("in onClickRegDevice.. view = " + view);

  // get registration id from shared preferences and thus check whether app is already registered or not.
  String regid = getRegistrationId(this);
  Log.i(this.toString(), "registration id from shared pref : " + regid);

  if (regid.isEmpty()) // if blank, then app is not yet registered
  {
   registerInBackground();
  }
  else
  {
   txt_reg_id.setText(regid);
  }
   
 }

 /**
  * this method will check whether device has google play services apk installed or not. if not then it will display appropriate dialog.
  */
 private boolean checkPlayServices()
 {
  int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
  if (resultCode != ConnectionResult.SUCCESS)
  {
   if (GooglePlayServicesUtil.isUserRecoverableError(resultCode))
   {
    GooglePlayServicesUtil.getErrorDialog(resultCode, this, PLAY_SERVICES_RESOLUTION_REQUEST).show();
   }
   else
   {
    Log.i(this.toString(), "This device is not supported.");
    finish();
   }
   return false;
  }
  return true;
 }

 /**
  * get gcm registration id from shared preferences. once app is registered to gcm and got the registration id, it will store that in shared
  * preferences.
  * 
  * returns blank when no registration id found in shared pref. if app was updated, then also it will return blank so that app can be registered
  * again.
  */
 private String getRegistrationId(Context context)
 {
  final SharedPreferences prefs = getGCMPreferences(context);
  String registrationId = prefs.getString(PROPERTY_REG_ID, "");
  if (registrationId.isEmpty())
  {
   Log.i(this.toString(), "Registration not found.");
   return "";
  }
  // Check if app was updated; if so, it must clear the registration ID
  // since the existing regID is not guaranteed to work with the new app version.
  int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
  int currentVersion = getAppVersion(context);
  if (registeredVersion != currentVersion)
  {
   Log.i(this.toString(), "App version changed.");
   return "";
  }
  return registrationId;
 }

 /**
  * store registration id and app version in shared preferences.
  */
 private void storeRegistrationId(Context context, String regId)
 {
  final SharedPreferences prefs = getGCMPreferences(context);

  int appVersion = getAppVersion(context);
  Log.i(this.toString(), "Saving regId on app version " + appVersion);

  SharedPreferences.Editor editor = prefs.edit();
  editor.putString(PROPERTY_REG_ID, regId);
  editor.putInt(PROPERTY_APP_VERSION, appVersion);
  editor.commit();
 }

 private SharedPreferences getGCMPreferences(Context context)
 {
  // This sample app persists the registration ID in shared preferences, but
  // how you store the regID in your app is up to you.
  return getSharedPreferences(MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
 }

 private static int getAppVersion(Context context)
 {
  try
  {
   PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
   return packageInfo.versionCode;
  }
  catch (NameNotFoundException e)
  {
   throw new RuntimeException("Could not get package name: " + e);
  }
 }

 private void registerInBackground()
 {
  new AsyncTask<String, Void, String>()
  {
   private String regId = "";

   @Override
   protected String doInBackground(String... params)
   {
    String msg = "";
    try
    {
     if (gcm == null)
     {
      gcm = GoogleCloudMessaging.getInstance(MainActivity.this);
     }
     regId = gcm.register(SENDER_ID);
     Log.i(this.toString(), "regId = "+regId);

     // You should send the registration ID to your server over HTTP,
     // so it can use GCM/HTTP or CCS to send messages to your app.
     // The request to your server should be authenticated if your app
     // is using accounts.
     // sendRegistrationIdToBackend();

     // For this demo: we don't need to send it because the device
     // will send upstream messages to a server that echo back the
     // message using the 'from' address in the message.

     // Persist the regID - no need to register again.
     storeRegistrationId(MainActivity.this, regId);
     
     msg = regId;
    }
    catch (IOException ex)
    {
     msg = "Error :" + ex.getMessage();
     // If there is an error, don't just keep trying to register.
     // Require the user to click a button again, or perform
     // exponential back-off.
    }
    return msg;
   }

   @Override
   protected void onPostExecute(String msg)
   {
    // setting registration id in edit text.
    txt_reg_id.setText(msg);
   }

  }.execute(null, null, null);
 }
}

Receive a message

Whenever your 3rd party application sends a message to GCM enabled android app, GCM framework will invoke WakefulBroadcastReceiver. so its WakefulBroadcastReceiver's responsibility to handle the message.
A WakefulBroadcastReceiver is a special type of broadcast receiver that takes care of creating and managing a partial wake lock for your app, so that your application go to sleep while processing the message.

WakefulBroadcastReceiver will invoke IntentService, which will be actually processing the message.

Following is the code for WakefulBroadcastReceiver. it will start the GcmIntentService when it will receive the message.

package com.example.gcm;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver
{
 @Override
 public void onReceive(Context context, Intent intent)
 {
  // Explicitly specify that GcmIntentService will handle the intent.
  ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
  
  // Start the service, keeping the device awake while it is launching.
  startWakefulService(context, (intent.setComponent(comp)));
  
  setResultCode(Activity.RESULT_OK);
 }
}

GcmIntentService will process the message according to the application requirement.
Here it will only send a notifications.

package com.example.gcm;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.google.android.gms.gcm.GoogleCloudMessaging;

public class GcmIntentService extends IntentService
{
 public static final int NOTIFICATION_ID = 1;
 private NotificationManager mNotificationManager;
 NotificationCompat.Builder builder;

 public GcmIntentService()
 {
  super("GcmIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent)
 {
  Bundle extras = intent.getExtras();
  GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

  // The getMessageType() intent parameter must be the intent you received
  // in your BroadcastReceiver.
  String messageType = gcm.getMessageType(intent);

  if (!extras.isEmpty())
  { // has effect of unparcelling Bundle
   /*
    * Filter messages based on message type. Since it is likely that GCM will be extended in the future with new message types, just ignore
    * any message types you're not interested in, or that you don't recognize.
    */

   if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType))
   {
    sendNotification("Send error: " + extras.toString());
   }
   else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType))
   {
    sendNotification("Deleted messages on server: " + extras.toString());    
   }
   else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) // If it's a regular GCM message, do some work.
   {
    // This loop represents the service doing some work.
    for (int i = 0; i < 5; i++)
    {
     Log.i(this.toString(), "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime());
     try
     {
      Thread.sleep(5000);
     }
     catch (InterruptedException e)
     {
     }
    }
    Log.i(this.toString(), "Completed work @ " + SystemClock.elapsedRealtime());
    
    // Post notification of received message.
    sendNotification("Received: " + extras.toString());
    
    Log.i(this.toString(), "Received: " + extras.toString());
   }
  }
  // Release the wake lock provided by the WakefulBroadcastReceiver.
  GcmBroadcastReceiver.completeWakefulIntent(intent);
 }
 
 // Put the message into a notification and post it.
 // This is just one simple example of what you might choose to do with
 // a GCM message.
 private void sendNotification(String msg)
 {
  mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

  PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);

  NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher)
    .setContentTitle("GCM Notification").setStyle(new NotificationCompat.BigTextStyle().bigText(msg)).setContentText(msg);

  mBuilder.setContentIntent(contentIntent);
  mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
 }
}

Implementing a GCM Server


Project Setup

Download helper libraries from : http://code.google.com/p/gcm/source/browse/#git%2Fgcm-server

Create a new java project in eclipse (gcm-server-sample)

Copy gcm-server/src/com folder to gcm-server-sample/src directory.

Create a new folder lib into gcm-server-sample project.

Copy gcm-server/lib/json_simple-1.1.jar file to gcm-server-sample/lib directory.

Open gcm-server-sample project properties. goto Java Build Path --> Libraries tab and add json_simple-1.1.jar file to build path.

Writing code to send message to android device

Create a new class /gcm-server-Sample/src/test/GCMServerTest.java . in this class we will write a code to send a message to android device.

package test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Sender;

public class GCMServerTest
{
 public static void main(String[] args)
 {
  try
  {
   sendMessage();
   System.out.println("message sent");
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
 }
 
 private static void sendMessage() throws IOException
 {
  String API_KEY = ""; //sender id got from google api console project
  String collpaseKey = "gcm_message"; //if messages are sent and not delivered yet to android device (as device might not be online), then only deliver latest message when device is online
  String messageStr = "message content here"; //actual message content
  String messageId = "APA91bGgGzVQWb88wkRkACGmHJROeJSyQbzLvh3GgP2CASK_NBsuIXH15HcnMta3e9ZXMhdPN6Z3FSD2Pezf6bhgUuM2CF74SgZbG4Zr57LA76VVaNvSi7XM7QEuAVLIiTsXnVq3QAUFDo-ynD316bF10JGT3ZOaSQ"; //gcm registration id of android device
  
  Sender sender = new Sender(API_KEY);
  Message.Builder builder = new Message.Builder();
  
  builder.collapseKey(collpaseKey);
  builder.timeToLive(30);
  builder.delayWhileIdle(true);
  builder.addData("message", messageStr);
  
  Message message = builder.build();
  
  List<String> androidTargets = new ArrayList<String>();
  //if multiple messages needs to be deliver then add more message ids to this list
  androidTargets.add(messageId);
  
  MulticastResult result = sender.send(message, androidTargets, 1);
  System.out.println("result = "+result);
  
  if (result.getResults() != null) 
  {
   int canonicalRegId = result.getCanonicalIds();
   System.out.println("canonicalRegId = "+canonicalRegId);
   
   if (canonicalRegId != 0) 
   {
            }
  }
  else 
  {
   int error = result.getFailure();
   System.out.println("Broadcast failure: " + error);
  }
 }
}

Sample App Downloads


Android sample app

You have to update your sender id (Google API project number)

Java server application for sending GCM messages

You have to update API key and registration id of android device to send message in source code.



Thursday, August 14, 2014

Migrating from Android SDK to Google Play Services SDK

If you are using android sdk for developing android applications then its time to migrate to Google Play Services sdk.

You might have noticed the below notification on new admob website.

On Aug. 1 2014, Google Play will stop accepting new or updated apps using the standalone Google Mobile Ads SDKs v6.4.1 or lower. Upgrade to the Google Play version of the Mobile Ads SDK to take advantage of new features, including automatic updates such as bug fixes.

If you are currently using android sdk then follow below steps to migrate to Google Play Services sdk.

1) Update some tools in existing Android SDK Manager.

Open Android SDK manager and install/update following tools as shown in below screenshot.

Android Support Repository
Android Support Library
Google Repository
Google Play services


2) Import Google Play Service Library Project into your workspace.

Library project is located in sdk directory : <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/

Import this library project into your Eclipse workspace. 

Click File > Import, select Android > Existing Android Code into Workspace, Select above directory as root directory.
library project will appear in project list. Select google-play-services_lib project and click on Finish.
Make sure to select ‘Copy projects into workspace’



3) Reference google play services library project into your android application project.

Right click on your android app project, goto Properties, select Android. 
In the Library section, click on Add , select google-play-services_lib project and click ok.



4) Modify your android application’s manifest file.

Open your app's manifest file and add the following tag as a child of the <application> element:

<meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

Thats it. Now you can use Google Play Services API into your android project.

Notes:

1) To prevent ProGuard from stripping away required classes, add the following lines in the <project_directory>/proguard-project.txt file:

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

2) You have to remove admob sdk jar file if you have in libs directory.

3) If you are using Google Play Services SDK for android application development, then your app will depend on google play store and google play services apk on client devices.

So before using the google play services features from your app, you should check whether client’s android device has compatible google play services apk or not?
You can perform this check on your activity’s onResume method.

Google play services library that you have just integrated, includes utility methods to check the compatibility of google play services on user’s android devices.

To verify the Google Play services version, call isGooglePlayServicesAvailable(). If the result code is SUCCESS, then the Google Play services APK is up-to-date and you can continue to make a connection. If, however, the result code is SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, or SERVICE_DISABLED, then the user needs to install an update. So, call GooglePlayServicesUtil.getErrorDialog() and pass it the result error code. This returns a Dialog you should show, which provides an appropriate message about the error and provides an action that takes the user to Google Play Store to install the update.

Migrate your code into new google play services SDK as per this google checklist 

Common problems you may encounter during this migration activity


1) In eclipse, google-play-service_lib project has error : unable to get system library for the project

If you got this error, then following these steps to fix it.

Right click on google-play-service_lib project
Go to properties
Click the Android section and select project build target as below.



2) Android Lint error while exporting app to apk : <key> is not translated in <language> error.

In "Window" > "Preferences" > "Android" > "Lint Error Checking"

Find the MissingTranslation line, and set it to Warning as seen below:




Monday, August 11, 2014

Invoking Web Services hosted over SSL (HTTPS) in JAVA

In this post, we will see how to invoke web services hosted over SSL (HTTPS) using JAVA.

If web services you are trying to call are not hosted over SSL, then you can refer to this post to generate web service client and invoke the web services : JAX-WS Web Service Client for Java

If your web services are hosted over SSL, and if you try to directly invoke those web services using java client, soapui, eclipse web service explorer or any other tool, then you might get following error.

IWAB0379E Unable to open https://abc.xyz.com:8443/TestService/test1?wsdl.
IWAB0135E An unexpected error has occurred.
WSDLException
WSDLException: faultCode=OTHER_ERROR: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

To fix this issue, you have to get SSL certificate the web service is using and have to import that certificate into your local JRE keystore.

Below is step by step guide how to do this.

1) Get certificate used by web service.
Enter web service url in firefox's address bar. it will display the security warning and option to trust the certificate.

Click on.
I understand the risk --> View --> Details tab --> Export.
save the certificate file on your local. (.crt or .cer).

2) Find the JDK path on your local machine. 

If you are using eclipse web service explorer or java program in eclipse to execute the web service then you can find the JDK used by your eclipse as below.

Open eclipse. go to Help menu --> About --> Installation details --> Configuration tab --> find javaw.exe path in the configuration.

lets say javaw.exe path is : "c:\Program Files\Java\jdk1.6.0_26\bin\javaw.exe"

3) Go to JRE directory of this javaw.

> cd c:\Program Files\Java\jdk1.6.0_26\jre\lib\security

take a backup of the file. "c:\Program Files\Java\jdk1.6.0_26\jre\lib\security\cacerts"
make sure that you have write permission on this file.

To add permission follow below steps
right click on the file . go to security tab --> edit --> add --> add your username --> check names --> ok --> check all access rights in permission box --> ok

4) Import certificate using below command.

keytool -import -keystore <full_path_to_cacerts> -alias <any_name_to_identify_your_cert> -file <full_path_to_cert_file>

For example:
> cd C:\Program Files\Java\jdk1.6.0_26\jre\bin

> keytool -import -keystore "C:\Program Files\Java\jdk1.6.0_26\jre\lib\security\cacerts" -alias ws.testservice.com -file "E:\abc\xyz\ws.testservice.com.crt"

this will ask for the password. enter "changeit".

after this it will ask for confirmation : enter "yes".

finally it should display the message like this : "Certificate was added to keystore".
there should not be any error at end.

5) check the timestamp of the cacerts file. it should be updated to the current timestamp.
verify the added certificate using the below command.

keytool -list -keystore "C:\Program Files\Java\jdk1.6.0_26\jre\lib\security\cacerts"

There must be an entry for your certificate alias added above.


6) restart eclipse and test the web service.


Thursday, August 7, 2014

Printing SOAP request/response messages while invoking web services in Java

In this post, we will see how to print SOAP messages sent from SOAP client and received from SOAP services in java. Also how to format the SOAP messages in Java so that they can be easily readable.

For how to create a webservice client you can refer to this post: JAX-WS Web Service Client for Java

Lets consider the service name as 'TestService' . So if you generated the webservice client as mentioned in above post using wsimport this should have generated below 2 java files out of others.

TestServiceService.java
TestService.java

To print SOAP request/response messages we have to write a SOAP handler and have to add that handler to the generated client's handler list.

Here is the code for adding the handler to SOAP client.

TestServiceService serviceImpl = new TestServiceService();
Testservice testservice = serviceImpl.getTestServicePort();

BindingProvider bindingProvider = (BindingProvider) testservice;
Binding binding = bindingProvider.getBinding();
List<Handler> handlerList = binding.getHandlerChain();

handlerList.add(new SOAPMessageWriterHandler());

This is the code for SOAP handler : SOAPMessageWriterHandler

import java.io.ByteArrayOutputStream;
import java.util.Set;

import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
  
public class SOAPMessageWriterHandler implements SOAPHandler<SOAPMessageContext> {  
  
    public boolean handleMessage(SOAPMessageContext smc) {  
  
        Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);  
  
        SOAPMessage message = smc.getMessage();  
  
        try
  {
   if (!outboundProperty.booleanValue()) {  
    System.out.println("SOAP Response : ");
    message.writeTo(System.out);   
   } else {  
    System.out.println("SOAP Request : ");
       message.writeTo(System.out);
   } 
  }
  catch (Exception e)
  {  
   e.printStackTrace();
  }    
        return outboundProperty;    
    }  
  
    public Set getHeaders() {  
        return null;  
    }  
  
    public boolean handleFault(SOAPMessageContext context) {  
        return true;  
    }  
  
    public void close(MessageContext context) {  
    }  
}  

Formatting SOAP messages

Above handler will print single line SOAP messages. If you want to format the SOAP messages you can use below code for that.

For this, you have to write a method that will convert SOAP message to string and then format the string using XML API of Java as below.

public static void printFormattedXML(SOAPMessage message) throws Exception
{
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
 message.writeTo(bout);
    String xml = bout.toString();

 Source xmlInput = new StreamSource(new StringReader(xml));
 StringWriter stringWriter = new StringWriter();
 StreamResult xmlOutput = new StreamResult(stringWriter);
 TransformerFactory transformerFactory = TransformerFactory.newInstance();
 transformerFactory.setAttribute("indent-number", 5);
 Transformer transformer = transformerFactory.newTransformer();
 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 transformer.transform(xmlInput, xmlOutput);
 String xmlString = xmlOutput.getWriter().toString();

 System.out.println(xmlString);
}

You can use this method in handler to format and print the messages.
Just replace System.out.println with this method.

JAX-WS Web Service Client for Java

This post will show you how to generate Web Service Client in Java and how to invoke the Web Service using the generated client.

To generate web service client we will use wsimport tool that comes with jdk. you can find the tool in JAVA_HOME/bin directory of your machines.

You will need wsdl file for this. If you have a url of wsdl then you can directly use that. Alternatively you can save the wsdl file on your local machine too.

Use below command to generate the java code for the client.

wsimport -keep <wsdl_path>

e.g. wsimport -keep "http://www.abc.com/MyService?WSDL" 
       wsimport -keep "c:/abc/xyz/mywsdl.wsdl"

-keep option will keep the generated java source files along with class files.

If webservice has additional headers to be passed , then use below command, to generate parameterized method for headers. In this you can pass header directly while invoking the web service methods rather than writing separate handlers for that.

wsimport -keep -XadditionalHeaders <wsdl_path>

This will generate the required java files into the current directory.

Invoking webservice using the generated client

Although you can run the client as a standalone java program without using any IDE, but it would be good if you use eclipse to run this.

Create a new "Java Project" in eclipse. copy all the files generated in src directory of your java project along with directory structure.

Lets consider the servicename as 'TestService' . So based on this service name, wsimport should have generated below 2 java files along with others.

TestServiceService.java

TestService.java

You can invoke your webservice using the below code snippet.
For this you can create a test class in your test project with main method. you can call the below code in your main method.

TestServiceService serviceImpl = new TestServiceService();
Testservice testservice = serviceImpl.getTestServicePort();

-- you can invoke any of the webservice method now.. like

testservice.test1();

Printing SOAP requests sent from SOAP client and responses received from SOAP services and formatting it.


To print and format SOAP messages you can refer to this post : Printing SOAP request/response messages while invoking web services in Java

Friday, August 1, 2014

How to get html content from android webview

There are 2 different ways to get html content from android webview.

1) Using Javascript
In this approach, url will be loaded by webview itself. Means each and every url will be invoked in the webview context. so webview will manage cookies and http session automatically.

2) Using Java only to get html content from webview.
In this approach, webview will not be responsible for fetching the content from remote servers. We have to handle invocation of urls and have to fetch html content using code and then we can perform whatever operations we require on content before setting it in the webview.
So maintaining cookies and http session will be our responsibility.
In the example snippet below for this approach will show a workaround for maintaining http session.


1) Using Javascript

First you have to configure javascript interface for webview, using below method

webview.addJavascriptInterface(new MyJavaScriptInterface(this), "HtmlViewer");

Here 1st parameter is the MyJavaScriptInterface class, that you have to write as below.
and 2nd parameter is the name of the javascript object you will use to call the method you write (showHTML).

class MyJavaScriptInterface 
{
 private Context ctx;

        MyJavaScriptInterface(Context ctx) 
 {
            this.ctx = ctx;
        }

       @JavascriptInterface
       public void showHTML(String html) 
       {
           //code to use html content here
 handlerForJavascriptInterface.post(new Runnable() {
   @Override
   public void run() 
   {
    Toast toast = Toast.makeText(this, "Page has been loaded in webview. html content :"+html_, Toast.LENGTH_LONG);
    toast.show();
   }});
        }
}

Note that annotation ‘ @JavascriptInterface’ on the showHTML method. it is mandatory to work this propertly for android >=4.2.
Another thing to keep in mind is that, from showHTML method, you will not able to access other components of your activity directly. You have to post runnable objects to a handler. and write all the logic to access activity’s components inside the run method of runnable.

Now everyting is setup. You just have to call showHTML method explicitly when the page loading is finished into the webview.
For this you have to implement WebviewClient which will be notified by android once page is loaded into the webview, and from that you can invoke showHTML method using javascript and pass the html content loaded into webview.
This can be achieved using below code.

webview.setWebViewClient(new WebViewClient() 
 {
  @Override
  public void onPageFinished(WebView view, String url) 
  {
   webview.loadUrl("javascript:window.HtmlViewer.showHTML" +
    "('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');");
  }
        });

Below is the complete activity code you can refer to if anything is missed in above code snippet.

public class TestActivity extends Activity
{
    Handler handlerForJavascriptInterface = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.webview);
        final WebView webview = (WebView) findViewById(R.id.browser);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.addJavascriptInterface(new MyJavaScriptInterface(this), "HtmlViewer");
        webview.setWebViewClient(new WebViewClient()
        {
            @Override
            public void onPageFinished(WebView view, String url)
            {
                webview.loadUrl("javascript:window.HtmlViewer.showHTML" +
                "('&lt;html&gt;'+document.getElementsByTagName('html')[0].innerHTML+'&lt;/html&gt;');");
            }
        }
        );
        webview.loadUrl("http://android-in-action.com/index.php?post/" +
        "Common-errors-and-bugs-and-how-to-solve-avoid-them");
    }
    class MyJavaScriptInterface
    {
        private Context ctx;
        MyJavaScriptInterface(Context ctx)
        {
            this.ctx = ctx;
        }
        public void showHTML(String html)
        {
            final html_ = html;
            handlerForJavascriptInterface.post(new Runnable()
            {
                @Override
                public void run()
                {
                    Toast toast = Toast.makeText(this, "Page has been loaded in webview. html content :"+html_, Toast.LENGTH_LONG);
                    toast.show();
                }
            }
            );
        }
    }
}

If you are using proguard while exporting your apps then following properties must be included in proguard-project.txt (proguard config file) .

-keepattributes JavascriptInterface
-keep public class com.mypackage.MyClass$MyJavaScriptInterface
-keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface
-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface { 
    <methods>; 
}


2) Using Java only to get html content from webview.

In this approach, you have to handle page loading events (page loading started, page loading finished) of webview based on your requirements and from there you have to use httpclient api to actually hit the url and get the content.
this way you will have the access to the html content returned by remote server and you can modify the content as per your needs before/after it is rendered into the webview.

Below is the complete code for this.

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.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
 * getting html content from webview using java only. not using javascript for
 * that.
 */
public class WebviewTestActivity1 extends Activity
{
 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_webview_test_activity1);

  final WebView webview = (WebView) findViewById(R.id.webview1);
  webview.getSettings().setJavaScriptEnabled(true);

  WebViewClient webViewClient = new WebViewClient()
  {
   /**
    * this method will be invoked when page started loading into the
    * webview. so you can use thid method if you want to modifiy the
    * content before it is rendered.
    */
   @Override
   public void onPageStarted(WebView view, String url, Bitmap favicon)
   {
    super.onPageStarted(view, url, favicon);
    String htmlContent = getRemoteContent(url);
    webview.loadDataWithBaseURL(url, htmlContent, null, "utf-8", url);
   }

   /**
    * this method will be invoked when page loading is finished into
    * the webview.
    */
   @Override
   public void onPageFinished(WebView view, String url)
   {
    // TODO Auto-generated method stub
    super.onPageFinished(view, url);
     // similar code as above method will go here .
   }
  };
  
  webview.setWebViewClient(webViewClient);
 }

 /**
  * this method will hit the remote url and get the content. this content
  * will be set in the webview.
  */
 private String getRemoteContent(String url)
 {
  HttpGet pageGet = new HttpGet(url);
  HttpClient client = new DefaultHttpClient();

  ResponseHandler<String> handler = new ResponseHandler<String>()
  {
   public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException
   {
    HttpEntity entity = response.getEntity();
    String html;

    if (entity != null)
    {
     html = EntityUtils.toString(entity);
     return html;
    }
    else
    {
     return null;
    }
   }
  };

  String pageHTML = null;
  try
  {
        pageHTML = client.execute(pageGet, handler);
        //if you want to manage http sessions then you have to add localContext as a third argument to this method and have uncomment below line to sync cookies.
         //syncCookies();
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }

  // you can filter your html content here if you wish before displaying
  // in webview

  return pageHTML;
 }
}

As stated above, this approach will not maintain cookies and http sessions.

So if you don't have requirement to maintain http session, then above code snippet is sufficient. Otherwise you have to implement additional code to achieve session management.
This is how you can achieve it.

First thing is you have to make your httpclient instance global. Means, you have to use same http client for executing each and every request.
Also you have to create global HttpContext and CookieStore objects, which will be used for executing http requests.

You can use below method to initialize the httpclient.

private static DefaultHttpClient httpClient = null; 
private static void initHttpClient()
{
 httpClient = new DefaultHttpClient();
 ClientConnectionManager cm = httpClient.getConnectionManager();
 SchemeRegistry sr = cm.getSchemeRegistry();

 sr.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
 sr.register(new Scheme("https", TrustAllSSLSocketFactory.getSocketFactory(), 443));

 cookieStore = new BasicCookieStore();
 localContext = new BasicHttpContext();
 localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}

Now you have to execute each and every request in localContext, which is using cookieStore internally.
And after invoking the request urls, you have to call below method to sync the cookies with webview.

private void syncCookies()
{  
 Cookie sessionInfo;
 List<Cookie> cookies = client.getCookieStore().getCookies();
 Log.d(TAG, "cookies = " + cookies);

 if (!cookies.isEmpty())
 {
  WebView webView = (WebView) findViewById(R.id.webView1);
  CookieSyncManager.createInstance(webView.getContext());
  CookieManager cookieManager = CookieManager.getInstance();

  for (Cookie cookie : cookies)
  {
   sessionInfo = cookie;
   String cookieString = sessionInfo.getName() + "=" + sessionInfo.getValue() + "; domain=" + sessionInfo.getDomain();
   Log.d(TAG, "cookieString = " + cookieString);
   cookieManager.setCookie("<your_host_address_here>", cookieString);
   CookieSyncManager.getInstance().sync();
  }
 }
}

Thats it! Feel free to add your feedback/comments if there are any.



Creating and Deploying Java Web Application on AWS using Elastic Beanstalk

This tutorial is for creating simple java web application using eclipse and then deploying it on AWS cloud. Video tutorial for creating/de...