Wednesday 1 October 2014

why we need to onUpgrade() method in SQLiteOpenHelper class?

onUpgrade is basically for handling new db changes (could be new columns addition,table addition) for any new version app.

Draping the table is not always necessary in on Upgrade it all depends on what your use case is. if the requirement is to not to persists the data from your older version of app then drop should help, but if its like changing schema then it should only have alter scripts.

When implementing the SQLiteOpenHelper, you pass a version parameter to the superclass constructor. This should be a static value you can change in future versions of your application (usually you'd want this to be a static final attribute of your SQLiteOpenHelper implementation, alongside the database name you pass to the superclass constructor).
Then, when you want to upadte the database, you increment the version parameter that will be used in your SQLiteOpenHelper implementation, and perform the SQL modifications you intend to do, programmatically, inside the onUpgrade method.
Say your DB started with table A in version 1, then you added table B in version 2 and table C in version 3, your onUpgrade method would look like:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) { // do stuff for migrating to version 2
        db.execSQL( ...create table B... );
    }
    if (oldVersion < 3) { // do stuff for migrating to version 3
        db.execSQL( ...create table C... );
    }
}
When the superclass constructor runs, it compares the version of the stored SQLite .db file against the version you passed as a parameter. If they differ, onUpgrade gets called.
I should check the implementation, but I assume onUpgrade is also called after onCreate if the database needs to be created, and there's a way to ensure all of the upgrades are executed (for example, by forcing all version numbers to be positive integers and initializing a newly created database with version 0).

Thursday 25 September 2014

What Handler and AsyncTask really help you with in Android?

If you see source code of AsyncTask and Handler, you will see their code purely in Java. (of course, there some exceptions, but that is not an important point).

What does it mean ? It means no magic in AsyncTask or Handler. They just make your job easier as a developer.

For example: If Program A calls method A(), method A() would run in a different thread with Program A.You can easily test by:

Thread t = Thread.currentThread();   
 int id = t.getId();

And why you should use new thread ? You can google for it. Many many reasons.

So, what is the difference ?

AsyncTask and Handler are written in Java (internally use a Thread), so everything you can do with Handler or AsyncTask, you can achieve using a Thread too.

What Handler and AsyncTask really help you with?

The most obvious reason is communication between caller thread and worker thread. (Caller Thread: A thread which calls the Worker Thread to perform some task.A Caller Thread may not be the UI Thread always).
 And, of course, you can communicate between two thread by other ways, but there are many disadvantages, for eg: Main thread isn’t thread-safe (in most of time), in other words, DANGEROUS.

That is why you should use Handler and AsyncTask. They do most of the work for you, you just need to know what methods to override.

Difference Handler and AsyncTask: Use AsyncTask when Caller thread is a UI Thread. This is what android document says:

    AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers

I want to emphasize on two points:

1) Easy use of the UI thread (so, use when caller thread is UI Thread).

2) No need to manipulate handlers. (means: You can use Handler instead of AsyncTask, but AsyncTask is an easier option).

There are many things in this post I haven’t said yet, for example: what is UI Thread, of why it easier. You must know some method behind each kind and use it, you will completely understand why..

@: when you read Android document, you will see:

    Handler allows you to send and process Message and Runnable objects associated with a thread’s Message-queue

They may seem strange at first.Just understand that, each thread has each message queue. (like a To do List), and thread will take each message and do it until message queue empty.
 (Ah, maybe like you finish your work and go to bed). So, when Handler communicates, it just gives a message to caller thread and it will wait to process. (sophiscate ? but you just know that,
Handler can communicate with caller thread in safe-way)

Sunday 14 September 2014

what's the difference between ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION.

ACCESS_COARSE_LOCATION is network based location access meaning the location is by your network, it could work with buildings or underground trains as long as you have access to your network while ACCESS_FINE_LOCATION is using gps thus your phone needs to be united with the sky (Correct me if im wrong here).

Location updates in android

<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...</manifest>
 
 
If you are using both NETWORK_PROVIDER and GPS_PROVIDER, then you need to request 
only the ACCESS_FINE_LOCATION permission, because it includes permission for both 
providers. (Permission for ACCESS_COARSE_LOCATION includes permission only for  
NETWORK_PROVIDER.) 

Thursday 31 July 2014

New Admob Installation:Google Play Services for Monetization



Recently Google announced that their Ad program for Android device Admob leaves old Admob SDK and moving to new Admob with Google Play Services package. Obsolete Admob publishers will not get impressions if don’t move to the new Admob with Play service. In this  tutorial I am going to unveil How to configure new Admob using Google Play services.


New Admob Installation

 

Problem:  Configure New Admob services for Android


Difficulty: Intermediate

Additional Requirements: copy of Google Play Services package, Admob Account, Admob publisher ID.

Step : 1 Create New Site/App

Note : You can skip this method if have an existing app if so save your Publisher ID for future enhancement.
1.  Go to Admob.com
2. Click on Sites & Apps tab  and Click Add Site/App
3.  Select Android from List
4.  Enter you App Name (example Ad Revenue Calculator)
5.  Enter the Android Package URL (example market://details?id=<com.bigknol.adrevenue.calculator>) make sure that the id points to correct package name of your project.
6. Select your Application category (Example Productivity)
7:  Add your App description
8.  Select Keyword Target Ads
9. Press continue button.
10. You can download the AdMob SDK after completing the above steps. Copy the App publisher ID from the final step.
Note : GoogleAdMobAdsSdk-6.4.1.jar is obsolete, you don’t want to use it on your app. 

Step 2: Install Google Play Services to SDK

1. Before you getting started with new Admob service you must install Google Play Services package in SDK, It will be stored in <android sdk>/extras/google/ directory. Open up your Android SDK Manager from Window menu of Eclipse IDE. Move down and go to Extras tick Google Play services (If you are targeting Android Froyo users you need to tick Google Play services for Froyo also) Install all required packages from Android SDK Manager.
New Admob Installation Step 1
2. Copy the Google Play services package.
Many developers forget to move a copy of extras package to workspace for making a reference to project. So you should take a copy of Google play services from <android sdk>/extras/google/ and paste it on your workspace.
3. Linking Google Play services package to project
If you want to use any class of AdMob package you must reference it properly. Otherwise you may get errors like “Admob  Class Not found Exceptions”.  How to link it?  First you need to import the google-play-services_lib to current workspace.
File > Import > Android > Existing Android Code Into Workspace
Browse it from google_play_services/libproject/google-play-services_lib.
Warning: Don’t browse the original Google play services libproject from <android-sdk>/extras/google/google_play_services. Make sure that you have taken libproject from the copied version of Google play services in the workspace.

Step 3: Referencing to Current project

Right click on your project Select Properties



New Admob Installation Step 3
 


Click on Android, press Add button from Library then choose the google-play-services_lib as library. Click on Apply button then finally OK


Admob Installation Step 4
 

Now you can use com.google.android.gms.ads methods and classes in your project.

Step 4:  Setting up Android Manifest File for New Admob SDK

Your android manifest file should have required permissions for executing AdMob. It includes INTERNET and ACCESS_NETWORK_STATE.
1. Add the following permissions (Before application Tag) to Manifest.xml



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







2. Add the meta tag to Manifest
You have to add a meta tag that tells you are using the version of google play. Add the following meta tag before closing the application tag.

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



3. You must register an AdActivity to Manifest file. It will be appeared inside the application tag.



<activity android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>

 
4. Save your Manifest File.

Step 5: Configure Admob Views in XML layout

Assume your main layout file is ad_test.xml (Displaying Ads in this layout) you must implement an xmlns to your main layout it might be relative or linear.

1
xmlns:ads="http://schemas.android.com/apk/res-auto"
ad_test.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 xmlns:ads="http://schemas.android.com/apk/res-auto"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".MyAds"
 android:id="@+id/TestAd"
 >
</RelativeLayout>

Next is to add a custom AdView to the ad_test layout.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 xmlns:ads="http://schemas.android.com/apk/res-auto"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".MyAds"
 android:id="@+id/TestAd"
 >
<com.google.android.gms.ads.AdView android:id="@+id/MyAdView"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 ads:adUnitId="XXXXXXX"
 ads:adSize="BANNER"
 />
 </RelativeLayout>

We have already copied the Publisher ID from the beginning of the tutorial; now replace the adUnitId XXXXXX using your on ID. Don’t forget to add adSize property.
You are done!

Step 6: Make Visible your Ads, and Test it on your Emulator, Device

Open your Main Activity File (example:  MyAds.java)

import android.app.Activity;
import android.os.Bundle;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
public class MyAds extends Activity {
private AdView adView;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_my_ads);
 adView=(AdView)findViewById(R.id.MyAdView);
 AdRequest adRequest=new AdRequest.Builder().build();
 adView.loadAd(adRequest);
 }
 @Override
 public void onPause() {
 adView.pause();
 super.onPause();
 }
@Override
 public void onResume() {
 super.onResume();
 adView.resume();
 }
@Override
 public void onDestroy() {
 adView.destroy();
 super.onDestroy();
 }
}

You may have noticed that, An instance of AdRequest is instantiated finally build it and load from OnCreate Method.
Warning : I recommend test mode of AdMob when you try the above activity.
How to use Test Mode in Admob ?
You might know Google is very strict for preventing fraud clicks against ads they publish through various program like adsense, admob etc. Invalid impressions of Admob ads may cause your account instantly.
You can use test mode by modifying the above code.

  adView=(AdView)findViewById(R.id.MyAdView);
  AdRequest adRequest=new AdRequest.Builder()
 .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
 .addTestDevice("7XXXXXXXXXXXXXXXXXXXXX").build();
 adView.loadAd(adRequest);


If you observe the above code, addTestDevice() method informs to request that you are using the ad test phase not the real ad serving phase. You may ask Where do I get the Device ID (7XXXXXXXXXXX) ?
You can capture it from your logcat when you test it on your emulator.



Admob Installation Step 5

Wednesday 30 July 2014

Multiple APK Upload into Same Application in Google play



Quickview

    Simultaneously publish different APKs for different device configurations
    You should publish multiple APKs only when it's not possible to support all desired devices with a single APK


Multiple APK support is a feature on Google Play that allows you to publish different APKs for your application that are each targeted to different device configurations.
Each APK is a complete and independent version of your application, but they share the same application listing on Google Play and must share the same package name and be
signed with the same release key. This feature is useful for cases in which your application cannot reach all desired devices with a single APK.

Android-powered devices may differ in several ways and it's important to the success of your application that you make it available to as many devices as possible.
Android applications usually run on most compatible devices with a single APK, by supplying alternative resources for different configurations
(for example, different layouts for different screen sizes) and the Android system selects the appropriate resources for the device at runtime. In a few cases,
 however, a single APK is unable to support all device configurations, because alternative resources make the APK file too big (greater than 50MB)
or other technical challenges prevent a single APK from working on all devices.

Although we encourage you to develop and publish a single APK that supports as many device configurations as possible, doing so is sometimes not possible.
 To help you publish your application for as many devices as possible, Google Play allows you to publish multiple APKs under the same application listing.
Google Play then supplies each APK to the appropriate devices based on configuration support you've declared in the manifest file of each APK.

By publishing your application with multiple APKs, you can:

    Support different OpenGL texture compression formats with each APK.
    Support different screen sizes and densities with each APK.
    Support different device feature sets with each APK.
    Support different platform versions with each APK.
    Support different CPU architectures with each APK (such as for ARM, x86, and MIPS, when your app uses the Android NDK).


Active APKs:

Before you can publish your application (whether publishing one or multiple APKs), you must "activate" your APK(s) from the APK files tab.
When you activate an APK, it moves into the list of Active APKs. This list allows you to preview which APK(s) you're about to publish.

If there are no errors, any "active" APK will be published to Google Play when you click the Publish button
(if the application is unpublished) or when you click the Save button (if the application is already published).


Simple mode and advanced mode


The Google Play Developer Console provides two modes for managing the APKs associated with your application:
simple mode and advanced mode. You can switch between these by clicking the link at the top-right corner of the APK files tab.

Simple mode is the traditional way to publish an application, using one APK at a time. In simple mode,
only one APK can be activated at a time. If you upload a new APK to update the application,
 clicking "Activate" on the new APK deactivates the currently active APK (you must then click Save to publish the new APK).

Advanced mode allows you to activate and publish multiple APKs that are each designed for
a specific set of device configurations. However, there are several rules based on the manifest declarations in each APK that determine
whether you're allowed to activate each APK along with others. When you activate an APK and it violates one of the rules,
you will receive an error or warning message. If it's an error, you cannot publish until you resolve the problem;
if it's a warning, you can publish the activated APKs, but there might be unintended consequences as to whether
your application is available for different devices. These rules are discussed more below.


Rules for multiple APKs


All APKs you publish for the same application must have the same package name and be signed with the same certificate key.
Each APK must have a different version code, specified by the android:versionCode attribute.
Each APK must not exactly match the configuration support of another APK.

That is, each APK must declare slightly different support for at least one of the supported Google Play filters (listed above).

Usually, you will differentiate your APKs based on a specific characteristic
(such as the supported texture compression formats), and thus, each APK will declare support for different devices. However, it's OK to publish multiple APKs that
overlap their support slightly. When two APKs do overlap (they support some of the same device configurations), a device that falls within that overlap range will
receive the APK with a higher version code (defined by android:versionCode).
You cannot activate a new APK that has a version code lower than that of the APK it's replacing. For example, say you have an active APK for
screen sizes small - normal with version code 0400, then try to replace it with an APK for the same screen sizes with version code 0300.
This raises an error, because it means users of the previous APK will not be able to update the application.
An APK that requires a higher API level must have a higher version code.

Using a version code scheme

This diagram is most important to understand first two digits are API Levels and next is screen size and last is App version please Look at the diagram.





 compatible screens: for manifest file include for different APK files one for Tab other for mobile
This is for tablet
 <compatible-screens>
        <screen android:screenSize="large" android:screenDensity="ldpi"/>
        <screen android:screenSize="large" android:screenDensity="mdpi"  />
        <screen android:screenSize="large" android:screenDensity="hdpi"/>
        <screen android:screenSize="large" android:screenDensity="xhdpi"  />
      
        <screen android:screenSize="xlarge" android:screenDensity="ldpi"/>
        <screen android:screenSize="xlarge" android:screenDensity="mdpi"  />
        <screen android:screenSize="xlarge" android:screenDensity="hdpi"/>
        <screen android:screenSize="xlarge" android:screenDensity="xhdpi"  />      
    </compatible-screens>


This is for Mobile

 <compatible-screens>
        <screen android:screenSize="small" android:screenDensity="ldpi"/>
        <screen android:screenSize="small" android:screenDensity="mdpi"  />
        <screen android:screenSize="small" android:screenDensity="hdpi"/>
        <screen android:screenSize="small" android:screenDensity="xhdpi"  />
       
        <screen android:screenSize="normal" android:screenDensity="ldpi"/>
        <screen android:screenSize="normal" android:screenDensity="mdpi"  />
        <screen android:screenSize="normal" android:screenDensity="hdpi"/>
        <screen android:screenSize="normal" android:screenDensity="xhdpi"  />
       
      
    </compatible-screens>

Android: startActivity and startActivityForResult

Here are some differences between startActivity and startActivityForResult

1. startActvity will start the activity you want to start without worrying about getting any result from new child activity started by startActivity to parent activity.

2. startAcitvityForResult() starts another activity from your activity and it expect to get some data from newly started child activity  by startAcitvityForResult()  and return that to parent activity.

Monday 14 July 2014

Types of broadcast :Local,Normal,Ordered and Sticky

Normal Broadcast
:- use sendBroadcast()
:- asynchronous broadcast
:- any receiver receives broadcast not any particular order
Ordered Broadcast
:- use sendOrderedBroadcast()
:- synchronous broadcast
:- receiver receives broadcast in priority base
:- we can also simply abort broadcast in this type
Local Broadcast
:- use only when broadcast is used only inside application
Sticky Broadcast
:- normal broadcast intent is not available any more after is was send and processed by the system.
:- use sendStickyBroadcast(Intent)
:- the corresponding intent is sticky, meaning the intent you are sending stays around after the broadcast is complete.
:- because of this others can quickly retrieve that data through the return value of registerReceiver(BroadcastReceiver, IntentFilter).
:- apart from this same as sendBroadcast(Intent).

How To Use startActivityForResult() in android

Activity;


The next logical step in learning the Android development is to look at how can you call or invoke one activity from another and get back data from the called activity back to the calling activity. For simplicity sake, let us name the first calling activity as parent activity and the invoked activity as the child activity.

For simplicity sake, I use an explicit intent for invoking the child activity. For simple invocation without expecting any data back, we use the method startActivity(). However, when we want a result to be returned by the child activity, we need to call it by the method startActivityForResult(). When the child activity finishes with the job, it should set the data in an intent and call the method setResult(resultcode, intent) to return the data through the intent.

The parent activity should have overridden the method onActivityResult(…) in order to be able to get the data and act upon it. 

NOTE: for successful execution of this sequence of events, the child activity should call finish() aftersetResult(..) in order to give back the handle to the parent activity.

In summary, here are the methods to implement in the parent activity:
  • 1.  startActivtyForResult(..)
  • 2.  onActivityResult(…)

The child Activity should complete the work as usual and finally call:
  • 1.  setResult(…)
  • 2.  finish()

The startActivity() is not only used for migration and it also can carry data within..,

Intent intent = new Intent(1stActivity.this, 2ndActivity.class);
//here putExtra() method is used for carry some data
intent.putExtra("data_name", actual_data);
startActivity(intent);

Now directly we can forward the data from one activity to another activity.

But if you wants to redirect to the 1st activity from the 2nd activity with data of 2nd activity we can use number of technology in Android like

  • Preference,
  • with the help of Bean,
  • startActivityForResult()

The Best & Simple way is to use startActivityForResult() method

Step-1 :

(In your 1st Activity class)
Use the syntax below When you want to move from the 1st Activity to the 2nd Activity

 use startActivityForResult() instead of using startActivity()

Intent intent= new Intent(1stActivity.this, 2ndActivity.class);
startActivityForResult(intent, 1);

//here we are passing two arguments the 1st one is Intent & the 2nd one is integer value for specification.


Step-2 :

(In your 2nd Activity class)

Use the Syntax below When you want to pass your data to the 1st Activity from the 2nd Activity


Intent i=new Intent();
i.putExtra("data_name",actual_data);
setResult(1, i);
//here we are passing two arguments the 1st one is Intent & the 2nd one is integer value for specification.
finish();

Step-3 :

(In your 1st Activity class)

// override the onActivityResult() method

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
{
super.onActivityResult(requestCode, resultCode, data);
//check with the int code specification 
//(in above activity we are using the integer value of "1")
if (requestCode == 1) 
{
if (null != data) 
  {
String lst_of_sr = data.getStringExtra("data_name");
Toast.makeText(getApplicationContext(), lst_of_sr  Toast.LENGTH_LONG).show();
  }
}
}
 Now you can back to the 1st activity with the data of 2nd activity.

Saturday 12 July 2014

Android: Screen Densities, Sizes, Configurations, and Icon Sizes

1. Definitions

  • resolution = number of pixels available in the display, scale-independent pixel = sp
  • density = how many pixels appear within a constant area of the display, dots per inch = dpi
  • size = amount of physical space available for displaying an interface, screen's diagonal, inch
  • density-independent pixel = virtual pixel that is independent of the screen density, dp

2. Density Classes

ClassNameDensityFactorDrawable FolderComment
ldpilow density120 dpisp = 3/4 * dpdrawable-ldpi
mdpimedium density160 dpisp = dpdrawable-mdpi OR drawablebaseline size, example: 320x480 (sp or dp)
hdpihigh density240 dpisp = 1.5 x dpdrawable-hdpiexample: 480x800 sp = 320x533 dp
xhdpiextra high density320 dpisp = 2 x dpdrawable-xhdpi
xxhdpiextra extra high density480 dpisp = 3 x dpdrawable-xxhdpi
xxxhdpiextra extra extra high density640 dpisp = 4 x dpdrawable-xxxhdpi

3. Icon Sizes (full / content)

DensityLauncherMenuAction BarStatus Bar and NotificationTabPop-up Dialog and List ViewSmall and Contextual
ldpi36x36 px36x36 / 24x24 px24x24 / 18x18 px18x18 / 16x16 px24x24 / 22x22 px24x24 px12x12 / 9x9 px
mdpi48x48 px48x48 / 32x32 px32x32 / 24x24 px24x24 / 22x22 px32x32 / 28x28 px32x32 px16x16 / 12x12 px
hdpi72x72 px72x72 / 48x48 px48x48 / 36x36 px36x36 / 33x33 px48x48 / 42x42 px48x48 px24x24 / 18x18 px
xhdpi96x96 px96x96 / 64x64 px64x64 / 48x48 px48x48 / 44x44 px64x64 / 56x56 px64x64 px32x32 / 24x24 px
xxhdpi144x144 px(1)(1)(1)(1)(1)(1)
xxxhdpi192x192 px(1)(1)(1)(1)(1)(1)
  • (1) Google documentation says: "Applications should not generally worry about this density; relying on XHIGH graphics being scaled up to it should be sufficient for almost all cases."
  • Launcher icons for Android Market: 512x512 px.

4. Screen Size Classes

ClassSize in dpLayout FolderExamplesComment
small426x320 dplayout-smalltypical phone screen (240x320 ldpi, 320x480 mdpi, etc.)
normal470x320 dplayout-normal OR layouttypical phone screen (480x800 hdpi)baseline size
large640x480 dplayout-largetweener tablet like the Streak (480x800 mdpi), 7" tablet (600x1024 mdpi)
xlarge960x720 dplayout-xlarge10" tablet (720x1280 mdpi, 800x1280 mdpi, etc.)

5. Example Screen Configurations

Screen SizeLow density (120), ldpiMedium density (160), mdpiHigh density (240), hdpiExtra high density (320), xhdpi
smallQVGA (240x320)
480x640
normalWQVGA400 (240x400)
WQVGA432 (240x432)
HVGA (320x480)WVGA800 (480x800)
WVGA854 (480x854)
600x1024
640x960
largeWVGA800 (480x800)(2)
WVGA854 (480x854)(2)
WVGA800 (480x800)(1)
WVGA854 (480x854)(1)
600x1024


xlarge1024x600WXGA (1280x800)(3)
1024x768
1280x768
1536x1152
1920x1152
1920x1200
2048x1536
2560x1536
2560x1600
  • (1) To emulate this configuration, specify a custom density of 160 when creating an Android Virtual Device that uses a WVGA800 or WVGA854 skin.
  • (2) To emulate this configuration, specify a custom density of 120 when creating an Android Virtual Device that uses a WVGA800 or WVGA854 skin.
  • (3) This skin is available with the Android 3.0 platform.

6. Screen Orientation

OrientationNameLayout Folder, Example
portportraitlayout-port-large
landlandscapelayout-land-normal OR layout-land

7. Best Practices

  1. Use wrap_content, match_parent, or dp units when specifying dimensions in an XML layout file.
    • except for defining text sizes: sp (scaling depends on user setting)
    • Note: fill_parent is deprecated since API level 8.
  2. Do not use hard coded pixel values in your application code.
  3. Do not use AbsoluteLayout.
    • deprecated since Android 1.5
    • alternative: RelativeLayout
  4. Supply alternative bitmap drawables for different screen densities.
  5. Provide a launcher icon for xxhdpi, but no other icons.

Wednesday 9 April 2014

Google Cloud Messaging For Android (GCM)

Important: C2DM has been officially deprecated as of June 26, 2012.

Interacting with Google cloud messaging server (GCM) is not difficult, all we need to do is get couple of configurations perfect and code wise just few number of lines. Overall setup involves IP Address, network proxy/firewall, API key and json objects which sounds like it will be difficult. Get a cup of green tea, start this on a positive note and we will get this done in half an hour.


Google-GCM-Notification
  1. Part A – A custom web application running in an application server. This will connect to Google GCM server and post notification.
  2. Part B – An application running in an Android virtual device (emulator) with an Intent capable of receiving the push notification from the GCM server.
  • We can use GCM as a notification engine. On an event it will send a notification to an android application and vice-versa.
  • A notification is a small piece of information. Using GCM, maximum 4kb of payload can be sent in a notification.
  • For now GCM is a free service – lets use responsibly :-)
  • Android App can receive the message from Google cloud messaging server (GCM) even if the app is not running via Intent broadcasting (our example app will demonstrate this).

It has been replaced by Google Cloud Messaging For Android (GCM)

This Tutorial will guide you how to create a sample simple application using the GCM functionality,

This demo will help you registering and unRegistering android device from GCM server

Getting your Sender ID

    STEP 1.  Register Here .
    STEP 2.  Click Create project. Your browser URL will change to something like:

    " https://code.google.com/apis/console/#project:4815162342 "


    Take note of the value after #project: (4815162342 in this example). This is your project ID, and it will be used later on as the GCM sender ID. This Id will be used by the Android Device while Registering for Push Notification.
    STEP 3. Choose Service tab from the left side menu on the web page. and turn on ” Google Cloud Messaging for Android “
    STEP 4. Go to API Access tab from the left menu of web page.

press Create new Server key and note down the generated key



CREATING APP FOR GCM

    Update ADT plugin 20 .
    update SDK > install Extras > Google Cloud Messaging for Android Library.
    Add gcm.jar to libs folder.(will be in the  android_sdk/extras/google/gcm after updating ADT and SDK)


Client code:-
Manifest file

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.gcm.demo.app"
    android:versionCode="1"
    android:versionName="1.0" >

    <!-- GCM requires Android SDK version 2.2 (API level 8) or above. -->
    <!-- The targetSdkVersion is optional, but it's always a good practice
         to target higher versions. -->
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>

    <!-- GCM connects to Google Services. -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- GCM requires a Google account. -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <!-- Keeps the processor from sleeping when a message is received. -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!--
     Creates a custom permission so only this app can receive its messages.

     NOTE: the permission *must* be called PACKAGE.permission.C2D_MESSAGE,
           where PACKAGE is the application's package name.
    -->
    <permission
        android:name="com.google.android.gcm.demo.app.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission
        android:name="com.google.android.gcm.demo.app.permission.C2D_MESSAGE" />


    <!-- This app has permission to register and receive data message. -->
    <uses-permission
        android:name="com.google.android.c2dm.permission.RECEIVE" />

    <!-- Main activity. -->
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="com.google.android.gcm.demo.app.DemoActivity"
            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>

        <!--
          BroadcastReceiver that will receive intents from GCM
          services and handle them to the custom IntentService.

          The com.google.android.c2dm.permission.SEND permission is necessary
          so only GCM services can send data messages for the app.
        -->
        <receiver
            android:name="com.google.android.gcm.GCMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <!-- Receives the actual messages. -->
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <!-- Receives the registration id. -->
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.google.android.gcm.demo.app" />
            </intent-filter>
        </receiver>


        <!--
          Application-specific subclass of GCMBaseIntentService that will
          handle received messages.

          By default, it must be named .GCMIntentService, unless the
          application uses a custom BroadcastReceiver that redefines its name.
        -->
        <service android:name=".GCMIntentService" />
    </application>

</manifest>


 CommonUtilities
package com.google.android.gcm.demo.app;

import android.content.Context;
import android.content.Intent;

public final class CommonUtilities {

    static final String SERVER_URL = "http://localhost:8080/GcmServer/";

    static final String SENDER_ID = "289783478537";

    static final String TAG = "GCMTesting";

    static final String DISPLAY_MESSAGE_ACTION = "com.google.android.gcm.demo.app.DISPLAY_MESSAGE";

    static final String EXTRA_MESSAGE = "message";

    static void displayMessage(Context context, String message) {
        Intent intent = new Intent(DISPLAY_MESSAGE_ACTION);
        intent.putExtra(EXTRA_MESSAGE, message);
        context.sendBroadcast(intent);
    }
}



DemoActivity

package com.google.android.gcm.demo.app;

import static com.google.android.gcm.demo.app.CommonUtilities.DISPLAY_MESSAGE_ACTION;
import static com.google.android.gcm.demo.app.CommonUtilities.EXTRA_MESSAGE;
import static com.google.android.gcm.demo.app.CommonUtilities.SENDER_ID;
import static com.google.android.gcm.demo.app.CommonUtilities.SERVER_URL;

import com.google.android.gcm.GCMRegistrar;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;

public class DemoActivity extends Activity {

    TextView mDisplay;
    AsyncTask<Void, Void, Void> mRegisterTask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        checkNotNull(SERVER_URL, "http://192.168.0.64:8080/Gcmserver/");
        checkNotNull(SENDER_ID, "289783478137");
        GCMRegistrar.checkDevice(this);
        GCMRegistrar.checkManifest(this);
        setContentView(R.layout.main);
        mDisplay = (TextView) findViewById(R.id.display);
        registerReceiver(mHandleMessageReceiver,
                new IntentFilter(DISPLAY_MESSAGE_ACTION));
        final String regId = GCMRegistrar.getRegistrationId(this);
        if (regId.equals("")) {
            GCMRegistrar.register(this, SENDER_ID);
        } else {
          
            if (GCMRegistrar.isRegisteredOnServer(this)) {
                mDisplay.append(getString(R.string.already_registered) + "\n");
            } else {
              
                final Context context = this;
                mRegisterTask = new AsyncTask<Void, Void, Void>() {

                    @Override
                    protected Void doInBackground(Void... params) {
                        boolean registered =
                                ServerUtilities.register(context, regId);
                      
                        if (!registered) {
                            GCMRegistrar.unregister(context);
                        }
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Void result) {
                        mRegisterTask = null;
                    }

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()) {
         
            case R.id.options_clear:
                mDisplay.setText(null);
                return true;
            case R.id.options_exit:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    protected void onDestroy() {
        if (mRegisterTask != null) {
            mRegisterTask.cancel(true);
        }
        unregisterReceiver(mHandleMessageReceiver);
        GCMRegistrar.onDestroy(this);
        super.onDestroy();
    }

    private void checkNotNull(Object reference, String name) {
        if (reference == null) {
            throw new NullPointerException(
                    getString(R.string.error_config, name));
        }
    }

    private final BroadcastReceiver mHandleMessageReceiver =
            new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String newMessage = intent.getExtras().getString(EXTRA_MESSAGE);
            mDisplay.append(newMessage + "\n");
        }
    };

}

GCMIntentService
package com.google.android.gcm.demo.app;

import static com.google.android.gcm.demo.app.CommonUtilities.SENDER_ID;
import static com.google.android.gcm.demo.app.CommonUtilities.displayMessage;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;
import com.google.android.gcm.GCMRegistrar;

public class GCMIntentService extends GCMBaseIntentService {

    @SuppressWarnings("hiding")
    private static final String TAG = "GCMIntentService";

    public GCMIntentService() {
        super(SENDER_ID);
    }

    @Override
    protected void onRegistered(Context context, String registrationId) {
        Log.i(TAG, "Device registered: regId = " + registrationId);
        displayMessage(context, getString(R.string.gcm_registered));
        ServerUtilities.register(context, registrationId);
    }

    @Override
    protected void onUnregistered(Context context, String registrationId) {
        Log.i(TAG, "Device unregistered");
        displayMessage(context, getString(R.string.gcm_unregistered));
        if (GCMRegistrar.isRegisteredOnServer(context)) {
            ServerUtilities.unregister(context, registrationId);
        } else {
            // This callback results from the call to unregister made on
            // ServerUtilities when the registration to the server failed.
            Log.i(TAG, "Ignoring unregister callback");
        }
    }

    @Override
    protected void onMessage(Context context, Intent intent) {
        Log.i(TAG, "Received message");
        String message = getString(R.string.gcm_message);
        displayMessage(context, message);
        // notifies user
        generateNotification(context, message);
    }

    @Override
    protected void onDeletedMessages(Context context, int total) {
        Log.i(TAG, "Received deleted messages notification");
        String message = getString(R.string.gcm_deleted, total);
        displayMessage(context, message);
        // notifies user
        generateNotification(context, message);
    }

    @Override
    public void onError(Context context, String errorId) {
        Log.i(TAG, "Received error: " + errorId);
        displayMessage(context, getString(R.string.gcm_error, errorId));
    }

    @Override
    protected boolean onRecoverableError(Context context, String errorId) {
        // log message
        Log.i(TAG, "Received recoverable error: " + errorId);
        displayMessage(context,
                getString(R.string.gcm_recoverable_error, errorId));
        return super.onRecoverableError(context, errorId);
    }

    /**
     * Issues a notification to inform the user that server has sent a message.
     */
    private static void generateNotification(Context context, String message) {
        int icon = R.drawable.ic_stat_gcm;
        long when = System.currentTimeMillis();
        NotificationManager notificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new Notification(icon, message, when);
        String title = context.getString(R.string.app_name);
        Intent notificationIntent = new Intent(context, DemoActivity.class);
        // set intent so it does not start a new activity
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent intent = PendingIntent.getActivity(context, 0,
                notificationIntent, 0);
        notification.setLatestEventInfo(context, title, message, intent);
        notification.flags |= Notification.FLAG_AUTO_CANCEL;
        notificationManager.notify(0, notification);
    }

}

ServerUtilities
package com.google.android.gcm.demo.app;

import static com.google.android.gcm.demo.app.CommonUtilities.SERVER_URL;
import static com.google.android.gcm.demo.app.CommonUtilities.TAG;
import static com.google.android.gcm.demo.app.CommonUtilities.displayMessage;

import com.google.android.gcm.GCMRegistrar;

import android.content.Context;
import android.util.Log;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

public final class ServerUtilities {

    private static final int MAX_ATTEMPTS = 5;
    private static final int BACKOFF_MILLI_SECONDS = 2000;
    private static final Random random = new Random();

    static boolean register(final Context context, final String regId) {
        Log.i(TAG, "registering device (regId = " + regId + ")");
        String serverUrl = SERVER_URL + "/register";
        Map<String, String> params = new HashMap<String, String>();
        params.put("regId", regId);
        long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
        for (int i = 1; i <= MAX_ATTEMPTS; i++) {
            Log.d(TAG, "Attempt #" + i + " to register");
            try {
                displayMessage(context, context.getString(
                        R.string.server_registering, i, MAX_ATTEMPTS));
                post(serverUrl, params);
                GCMRegistrar.setRegisteredOnServer(context, true);
                String message = context.getString(R.string.server_registered);
                CommonUtilities.displayMessage(context, message);
                return true;
            } catch (IOException e) {
                Log.e(TAG, "Failed to register on attempt " + i, e);
                if (i == MAX_ATTEMPTS) {
                    break;
                }
                try {
                    Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    // Activity finished before we complete - exit.
                    Log.d(TAG, "Thread interrupted: abort remaining retries!");
                    Thread.currentThread().interrupt();
                    return false;
                }
                // increase backoff exponentially
                backoff *= 2;
            }
        }
        String message = context.getString(R.string.server_register_error,
                MAX_ATTEMPTS);
        CommonUtilities.displayMessage(context, message);
        return false;
    }

    static void unregister(final Context context, final String regId) {
        Log.i(TAG, "unregistering device (regId = " + regId + ")");
        String serverUrl = SERVER_URL + "/unregister";
        Map<String, String> params = new HashMap<String, String>();
        params.put("regId", regId);
        try {
            post(serverUrl, params);
            GCMRegistrar.setRegisteredOnServer(context, false);
            String message = context.getString(R.string.server_unregistered);
            CommonUtilities.displayMessage(context, message);
        } catch (IOException e) {

            String message = context.getString(
                    R.string.server_unregister_error, e.getMessage());
            CommonUtilities.displayMessage(context, message);
        }
    }

    private static void post(String endpoint, Map<String, String> params)
            throws IOException {
        URL url;
        try {
            url = new URL(endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("invalid url: " + endpoint);
        }
        StringBuilder bodyBuilder = new StringBuilder();
        Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
        // constructs the POST body using the parameters
        while (iterator.hasNext()) {
            Entry<String, String> param = iterator.next();
            bodyBuilder.append(param.getKey()).append('=')
                    .append(param.getValue());
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }
        String body = bodyBuilder.toString();
        Log.v(TAG, "Posting '" + body + "' to " + url);
        byte[] bytes = body.getBytes();
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setFixedLengthStreamingMode(bytes.length);
            conn.getPermission();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded;charset=UTF-8");
            // post the request
            OutputStream out = conn.getOutputStream();
            out.write(bytes);
            out.close();
            // handle the response
            int status = conn.getResponseCode();
            if (status != 200) {
                throw new IOException("Post failed with error code " + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
}

GCM server Code 

ApiKeyInitializer

package com.google.android.gcm.demo.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;


public class ApiKeyInitializer implements ServletContextListener {

  static final String ATTRIBUTE_ACCESS_KEY = "AIzaSyDM-scFomGgcvBj9oR04Bn29sqv121SrTw";

  private static final String PATH = "/api.key";

  private final Logger logger = Logger.getLogger(getClass().getName());

  @Override
  public void contextInitialized(ServletContextEvent event) {
    logger.info("Reading " + PATH + " from resources (probably from " +
        "WEB-INF/classes");
    String key = getKey();
    event.getServletContext().setAttribute(ATTRIBUTE_ACCESS_KEY, key);
  }

  protected String getKey() {
    InputStream stream = Thread.currentThread().getContextClassLoader()
        .getResourceAsStream(PATH);
    if (stream == null) {
      throw new IllegalStateException("Could not find file " + PATH +
          " on web resources)");
    }
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
    try {
      String key = reader.readLine();
      return key;
    } catch (IOException e) {
      throw new RuntimeException("Could not read file " + PATH, e);
    } finally {
      try {
        reader.close();
      } catch (IOException e) {
        logger.log(Level.WARNING, "Exception closing " + PATH, e);
      }
    }
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
  }

}
BaseServlet

package com.google.android.gcm.demo.server;

import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@SuppressWarnings("serial")
abstract class BaseServlet extends HttpServlet {

  // change to true to allow GET calls
  static final boolean DEBUG = true;

  protected final Logger logger = Logger.getLogger(getClass().getName());

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    if (DEBUG) {
      doPost(req, resp);
    } else {
      super.doGet(req, resp);
    }
  }

  protected String getParameter(HttpServletRequest req, String parameter)
      throws ServletException {
    String value = req.getParameter(parameter);
    if (value == null || value.trim().isEmpty()) {
      if (DEBUG) {
        StringBuilder parameters = new StringBuilder();
        @SuppressWarnings("unchecked")
        Enumeration<String> names = req.getParameterNames();
        while (names.hasMoreElements()) {
          String name = names.nextElement();
          String param = req.getParameter(name);
          parameters.append(name).append("=").append(param).append("\n");
        }
        logger.fine("parameters: " + parameters);
      }
      throw new ServletException("Parameter " + parameter + " not found");
    }
    return value.trim();
  }

  protected String getParameter(HttpServletRequest req, String parameter,
      String defaultValue) {
    String value = req.getParameter(parameter);
    if (value == null || value.trim().isEmpty()) {
      value = defaultValue;
    }
    return value.trim();
  }

  protected void setSuccess(HttpServletResponse resp) {
    setSuccess(resp, 0);
  }

  protected void setSuccess(HttpServletResponse resp, int size) {
    resp.setStatus(HttpServletResponse.SC_OK);
    resp.setContentType("text/plain");
    resp.setContentLength(size);
  }

}
Datastore
package com.google.android.gcm.demo.server;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * Simple implementation of a data store using standard Java collections.
 * <p>
 * This class is neither persistent (it will lost the data when the app is
 * restarted) nor thread safe.
 */
public final class Datastore {

  private static final List<String> regIds = new ArrayList<String>();
  private static final Logger logger =
      Logger.getLogger(Datastore.class.getName());

  private Datastore() {
    throw new UnsupportedOperationException();
  }

  /**
   * Registers a device.
   *
   * @param regId device's registration id.
   */
  public static void register(String regId) {
    logger.info("Registering " + regId);
    regIds.add(regId);
  }

  /**
   * Unregisters a device.
   *
   * @param regId device's registration id.
   */
  public static void unregister(String regId) {
    logger.info("Unregistering " + regId);
    regIds.remove(regId);
  }

  /**
   * Gets all registered devices.
   */
  public static List<String> getDevices() {
    return regIds;
  }

}
HomeServlet

package com.google.android.gcm.demo.server;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet that adds display number of devices and button to send a message.
 * <p>
 * This servlet is used just by the browser (i.e., not device) and contains the
 * main page of the demo app.
 */
@SuppressWarnings("serial")
public class HomeServlet extends BaseServlet {

  static final String ATTRIBUTE_STATUS = "status";

  /**
   * Displays the existing messages and offer the option to send a new one.
   */
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("text/html");
    PrintWriter out = resp.getWriter();

    out.print("<html><body>");
    out.print("<head>");
    out.print("  <title>GCM Demo</title>");
    out.print("  <link rel='icon' href='favicon.png'/>");
    out.print("</head>");
    String status = (String) req.getAttribute(ATTRIBUTE_STATUS);
    if (status != null) {
      out.print(status);
    }
    List<String> devices = Datastore.getDevices();
    if (devices.isEmpty()) {
      out.print("<h2>No devices registered!</h2>");
    } else {
      out.print("<h2>" + devices.size() + " device(s) registered!</h2>");
      out.print("<form name='form' method='POST' action='sendAll'>");
      out.print("<input type='submit' value='Send Message' />");
      out.print("</form>");
    }
    out.print("</body></html>");
    resp.setStatus(HttpServletResponse.SC_OK);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    doGet(req, resp);
  }

}

RegisterServlet

package com.google.android.gcm.demo.server;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@SuppressWarnings("serial")
public class RegisterServlet extends BaseServlet {

  private static final String PARAMETER_REG_ID = "regId";

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException {
    String regId = getParameter(req, PARAMETER_REG_ID);
    Datastore.register(regId);
    setSuccess(resp);
  }

}
SendAllMessagesServlet

package com.google.android.gcm.demo.server;

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

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

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet that adds a new message to all registered devices.
 * <p>
 * This servlet is used just by the browser (i.e., not device).
 */
@SuppressWarnings("serial")
public class SendAllMessagesServlet extends BaseServlet {

  private Sender sender;

  @Override
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    sender = newSender(config);
  }

  /**
   * Creates the {@link Sender} based on the servlet settings.
   */
  protected Sender newSender(ServletConfig config) {
    String key = (String) config.getServletContext()
        .getAttribute(ApiKeyInitializer.ATTRIBUTE_ACCESS_KEY);
    return new Sender(key);
  }

  /**
   * Processes the request to add a new message.
   */
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    List<String> devices = Datastore.getDevices();
    StringBuilder status = new StringBuilder();
    if (devices.isEmpty()) {
      status.append("Message ignored as there is no device registered!");
    } else {
      List<Result> results;
      // NOTE: check below is for demonstration purposes; a real application
      // could always send a multicast, even for just one recipient
      if (devices.size() == 1) {
        // send a single message using plain post
        String registrationId = devices.get(0);
        Message message = new Message.Builder().build();
        Result result = sender.send(message, registrationId, 1);
        results = Arrays.asList(result);
      } else {
        // send a multicast message using JSON
        Message message = new Message.Builder().build();
        MulticastResult result = sender.send(message, devices, 1);
        results = result.getResults();
      }
      // analyze the results
      for (int i = 0; i < devices.size(); i++) {
        Result result = results.get(i);
        if (result.getMessageId() != null) {
          status.append("Succesfully sent message to device #").append(i);
          String canonicalRegId = result.getCanonicalRegistrationId();
          if (canonicalRegId != null) {
            // same device has more than on registration id: update it
            logger.finest("canonicalRegId " + canonicalRegId);
            devices.set(i, canonicalRegId);
            status.append(". Also updated registration id!");
          }
        } else {
          String error = result.getErrorCodeName();
          if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
            // application has been removed from device - unregister it
            status.append("Unregistered device #").append(i);
            String regId = devices.get(i);
            Datastore.unregister(regId);
          } else {
            status.append("Error sending message to device #").append(i)
                .append(": ").append(error);
          }
        }
        status.append("<br/>");
      }
    }
    req.setAttribute(HomeServlet.ATTRIBUTE_STATUS, status.toString());
    getServletContext().getRequestDispatcher("/home").forward(req, resp);
  }

}
UnregisterServlet

package com.google.android.gcm.demo.server;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet that unregisters a device, whose registration id is identified by
 * {@link #PARAMETER_REG_ID}.
 * <p>
 * The client app should call this servlet everytime it receives a
 * {@code com.google.android.c2dm.intent.REGISTRATION} with an
 * {@code unregistered} extra.
 */
@SuppressWarnings("serial")
public class UnregisterServlet extends BaseServlet {

  private static final String PARAMETER_REG_ID = "regId";

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException {
    String regId = getParameter(req, PARAMETER_REG_ID);
    Datastore.unregister(regId);
    setSuccess(resp);
  }

}