Sunday, March 31, 2013

Create your Treasure Map on Android

Treasure Map on Android


Refer to the post "Embed Google Map in WebView", you can create your own Treasure Map easily, by copy the link URL from http://maps.google.com/ with Treasure Mode selected, and replace it to mapPath in the MainActivity code.

Get the URL from Google Maps with Threasure Mode selected


Remark: I don't know will it back to normal tomorrow:)


FYI:



How to get Traffic come from International Space Station

If you have install Google Analytics, check your Real-Time report of Locations within today, you have 100% traffic from International Space Station!

100% Traffic from International Space Station

Happy April Fools Day:)

Google Nose!? Is it a April Fools Joke?

Is it a April Fools Joke? http://www.google.com/nose/




Introducing Google Nose

We're excited to announce our newest addition to Search: Google Nose. What do wet dogs smell like? Google Nose! How about victory? Google Nose! Try searching on Google for "wet dog" and explore other smells that people sniffed for, or visit google.com/nose to learn more. Happy smelling!

ADK Example: Control Arduino Due LED from Android device



Example to show how to implement with ADK, to control LED on Arduino Due from Android device.

Refer to my another blog for Arduino:

Draw Polyline from touched location to MyLocation

[Remark@2015-11-01: 
getMyLocation() method is deprecated.
use com.google.android.gms.location.FusedLocationProviderApi instead. FusedLocationProviderApi provides improved location finding and power usage and is used by the "My Location" blue dot. See the MyLocationDemoActivity in the sample applications folder for example example code, or the Location Developer Guide.]

In this exercise, Google Maps Android API v2 is implemented with MyLocation enabled. When user LongClick on the Map, a marker will be drawn, and a Polyline from touched location to MyLocation will be drawn if MyLocation is available.

Polyline from touched location to MyLocation


To get MyLocation programmatically, you have to enable my-location layer by calling setMyLocationEnabled(true). Then call getMyLocation(), it returns the currently displayed user location, or null if there is no location data available. Please noted that MyLocation may be need long time to available.

MainActivity.java
package com.example.androidmapex;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;

import android.location.Location;
import android.os.Bundle;
import android.widget.Toast;
import android.app.Activity;
import android.app.FragmentManager;

public class MainActivity extends Activity{
 
 private GoogleMap myMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        FragmentManager myFragmentManager = getFragmentManager();
        MapFragment myMapFragment1 = 
          (MapFragment)myFragmentManager.findFragmentById(R.id.map1);

        myMap = myMapFragment1.getMap();
        myMap.setMyLocationEnabled(true);
        myMap.setOnMapLongClickListener(myOnMapLongClickListener);

    }
 
 OnMapLongClickListener myOnMapLongClickListener =
   new OnMapLongClickListener(){

    @Override
    public void onMapLongClick(LatLng point) {
     myMap.addMarker(new MarkerOptions()
          .position(point)
          .title(point.toString()));
     
     Location myLocation = myMap.getMyLocation();
     if(myLocation == null){
      Toast.makeText(getApplicationContext(), 
        "My location not available", 
        Toast.LENGTH_LONG).show();
     }else{
      PolylineOptions polylineOptions = new PolylineOptions();
      polylineOptions.add(point);
      polylineOptions.add(
        new LatLng(myLocation.getLatitude(), myLocation.getLongitude()));
      myMap.addPolyline(polylineOptions);
     }
    }
  
 };
    
}


layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="5px"
        class="com.google.android.gms.maps.MapFragment"/>

</LinearLayout>


download filesDownload the files.



The series:
A simple example using Google Maps Android API v2, step by step.

Saturday, March 30, 2013

Embed Google Map in WebView

Embed Google Map in WebView


To load your custom Map, open it in PC, copy the Short URL in share option. It will be used later.

copy the Short URL in share option


MainActivity.java, load mapPath with the Short URL in share option.
package com.example.androidwebmap;

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

public class MainActivity extends Activity {
 
 WebView myWebView;
 
 String mapPath = "https://maps.google.com/?ll=37.0625,-95.677068&spn=29.301969,56.513672&t=h&z=4";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  myWebView = (WebView)findViewById(R.id.mapview);
  myWebView.getSettings().setJavaScriptEnabled(true);
  myWebView.setWebViewClient(new WebViewClient());
  
  myWebView.loadUrl(mapPath);
 }

}


Modify layout to add a MapView.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <WebView
        android:id="@+id/mapview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Notes:
- Permission of "android.permission.INTERNET" is need.
- Not all function can work on WebView! such as user cannot touch the marker on map.

download filesDownload the files.

Retain instance of MapFragment

In the last exercise of "Dual MapFragment", if the device orientation changed, the display area and zoom level will be kept, but the added marker will disappear.

In this exercise, we are going to modify map2 from last post, to retain instance after orientation changed.

Retain instance of MapFragment


To keep the markers after orientation changed, create custom MapFragment (RetainMapFragment), override onActivityCreated() call setRetainInstance(true).
package com.example.androidmapex;

import android.os.Bundle;

import com.google.android.gms.maps.MapFragment;

public class RetainMapFragment extends MapFragment {

 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
  setRetainInstance(true);
 }

}


Modify layout file to use "com.example.androidmapex.RetainMapFragment" class on map2.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map1"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="2"
        android:layout_margin="5px"
        class="com.google.android.gms.maps.MapFragment"/>
    <fragment
        android:id="@+id/map2"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="3"
        android:layout_margin="5px"
        class="com.example.androidmapex.RetainMapFragment"/>

</LinearLayout>


Modify MainActivity to use define myMapFragment2 as RetainMapFragment.
package com.example.androidmapex;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

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

public class MainActivity extends Activity{
 
 private GoogleMap myMap1, myMap2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        FragmentManager myFragmentManager = getFragmentManager();
        MapFragment myMapFragment1 = 
          (MapFragment)myFragmentManager.findFragmentById(R.id.map1);
        RetainMapFragment myMapFragment2 = 
          (RetainMapFragment)myFragmentManager.findFragmentById(R.id.map2);
        myMap1 = myMapFragment1.getMap();
        myMap2 = myMapFragment2.getMap();
        
        myMap1.setOnMapLongClickListener(my1_OnMapLongClickListener);
        myMap2.setOnMapLongClickListener(my2_OnMapLongClickListener);
    }
 
 OnMapLongClickListener my1_OnMapLongClickListener =
   new OnMapLongClickListener(){

    @Override
    public void onMapLongClick(LatLng point) {
     myMap1.addMarker(new MarkerOptions()
          .position(point)
          .title(point.toString()));
    }
  
 };
 
 OnMapLongClickListener my2_OnMapLongClickListener =
   new OnMapLongClickListener(){

    @Override
    public void onMapLongClick(LatLng point) {
     
     BitmapDescriptor bitmapDescriptor = 
       BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE);
     
     myMap2.addMarker(new MarkerOptions()
          .position(point)
          .icon(bitmapDescriptor)
          .title(point.toString()));
    }
  
 };
    
}


Related:
- Retain data using Fragment API setRetainInstance()


download filesDownload the files.



The series:
A simple example using Google Maps Android API v2, step by step.

Thursday, March 28, 2013

Dual MapFragment of Google Maps Android API v2

This exercise demonstrate how to embed two MapFragment in a Activity.

Dual MapFragment


Modify layout file to have two fragment of "com.google.android.gms.maps.MapFragment".
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map1"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="2"
        android:layout_margin="5px"
        class="com.google.android.gms.maps.MapFragment"/>
    <fragment
        android:id="@+id/map2"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="3"
        android:layout_margin="5px"
        class="com.google.android.gms.maps.MapFragment"/>

</LinearLayout>


Modify MainActivity.java to handle the Maps separately.
package com.example.androidmapex;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

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

public class MainActivity extends Activity{
 
 private GoogleMap myMap1, myMap2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        FragmentManager myFragmentManager = getFragmentManager();
        MapFragment myMapFragment1 = 
          (MapFragment)myFragmentManager.findFragmentById(R.id.map1);
        MapFragment myMapFragment2 = 
          (MapFragment)myFragmentManager.findFragmentById(R.id.map2);
        myMap1 = myMapFragment1.getMap();
        myMap2 = myMapFragment2.getMap();
        
        myMap1.setOnMapLongClickListener(my1_OnMapLongClickListener);
        myMap2.setOnMapLongClickListener(my2_OnMapLongClickListener);
    }
 
 OnMapLongClickListener my1_OnMapLongClickListener =
   new OnMapLongClickListener(){

    @Override
    public void onMapLongClick(LatLng point) {
     myMap1.addMarker(new MarkerOptions()
          .position(point)
          .title(point.toString()));
    }
  
 };
 
 OnMapLongClickListener my2_OnMapLongClickListener =
   new OnMapLongClickListener(){

    @Override
    public void onMapLongClick(LatLng point) {
     
     BitmapDescriptor bitmapDescriptor = 
       BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE);
     
     myMap2.addMarker(new MarkerOptions()
          .position(point)
          .icon(bitmapDescriptor)
          .title(point.toString()));
    }
  
 };
    
}


download filesDownload the files.



The series:
A simple example using Google Maps Android API v2, step by step.

Google Maps Engine Lite (Beta) launched

Google is launching Google Maps Engine Lite (Beta) helping you create advanced custom maps to share with collaborators and also publish to the web. You can visualize and map more data, import locations from a spreadsheet, use layers to visualize different types of content, or simply draw and add places, lines, and shapes.

Visit: Log-in your google account and browse Google Maps Engine Lite

Google Maps Engine Lite
Google Maps Engine Lite
Google Maps Engine Lite


- Learn more about Maps Engine Lite (Beta)

Wednesday, March 27, 2013

Stop IntentService

I have numbers of post about IntentService before; include Perform background processing with IntentService, Share IntentService among Fragments, Send data from IntentService to Activity, via additional Broadcast and Generate Notification in IntentService. This post is a good exercise to understand the life-cycle of IntentService.

To stop a IntentService, call the method stopService (Intent service). It request that a given application service be stopped. If the service is not running, nothing happens. Otherwise it is stopped. Note that calls to startService() are not counted -- this stops the service no matter how many times it was started. So imple? not at all.

In this exercise, buttons are added to start and stop IntentService. It can be noted that:
  • If you click the "Start IntentService" multi times, only one request will be processed at a time, All requests are handled on a single worker thread. The extra requests will be place in a queue.
  • If you click the "Start IntentService" multi times, only ONE call of stopService() is need to stop all request.
  • If you remove the checking on the boolean stopped, in onHandleIntent() of MyIntentService.java; call stopService() will call onDestroy() of MyIntentService, but the code in onHandleIntent() will keep running until finish by itself. Because onHandleIntent() is running on another worker thread.

Stop IntentService


Layout file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start IntentService" />
    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop IntentService" />
    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        style="?android:attr/progressBarStyleHorizontal"
        android:max="10"
        android:progress="0"/>

</LinearLayout>


MainActivity.java
package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {

 Button buttonStart, buttonStop;
 TextView textResult;
 ProgressBar progressBar;
 
 private MyBroadcastReceiver myBroadcastReceiver;
 private MyBroadcastReceiver_Update myBroadcastReceiver_Update;
 
 Intent intentMyIntentService;
 int numberOfIntentService;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textResult = (TextView)findViewById(R.id.result);
  progressBar = (ProgressBar)findViewById(R.id.progressbar);
  buttonStart = (Button)findViewById(R.id.start);
  buttonStop = (Button)findViewById(R.id.stop);
  
  buttonStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    numberOfIntentService++;
    
    //prepare String passing to intentMyIntentService
    String msgToIntentService = "Android-er: " + numberOfIntentService;

    //Start MyIntentService
    intentMyIntentService = new Intent(MainActivity.this, MyIntentService.class);
    intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, msgToIntentService);
    startService(intentMyIntentService);
   }});
  
  buttonStop.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    if(intentMyIntentService != null){
     stopService(intentMyIntentService);
     intentMyIntentService = null;
    }
   }});

  numberOfIntentService = 0;
  
  myBroadcastReceiver = new MyBroadcastReceiver();
  myBroadcastReceiver_Update = new MyBroadcastReceiver_Update();
  
  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
  
  IntentFilter intentFilter_update = new IntentFilter(MyIntentService.ACTION_MyUpdate);
  intentFilter_update.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver_Update, intentFilter_update);
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
  unregisterReceiver(myBroadcastReceiver_Update);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }
 }
 
 public class MyBroadcastReceiver_Update extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   int update = intent.getIntExtra(MyIntentService.EXTRA_KEY_UPDATE, 0);
   progressBar.setProgress(update);
  }
 }

}


MyIntentService.java
package com.example.androidintentservice;

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.support.v4.app.NotificationCompat;
import android.widget.Toast;

public class MyIntentService extends IntentService {
 
 private static final int MY_NOTIFICATION_ID=1;
 private static final int MY_DESTORY_NOTIFICATION_ID=2;
 NotificationManager notificationManager;
 Notification myNotification;
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String ACTION_MyUpdate = "com.example.androidintentservice.UPDATE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 public static final String EXTRA_KEY_UPDATE = "EXTRA_UPDATE";
 String msgFromActivity;
 String extraOut;
 
 boolean success;
 boolean stopped;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
  success = false;
  stopped = false;
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  msgFromActivity = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Hello: " +  msgFromActivity;
  
  //total 10 sec
  for(int i = 0; i <=10; i++){
   
   try {
    Thread.sleep(1000); //every 1 sec
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
   //--- Try to comment it ---//
   if(stopped){
    break;
   }
   
   //send update 
   Intent intentUpdate = new Intent();
   intentUpdate.setAction(ACTION_MyUpdate);
   intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
   intentUpdate.putExtra(EXTRA_KEY_UPDATE, i);
   sendBroadcast(intentUpdate);
   
   PendingIntent pendingIntent_doNothing = PendingIntent.getActivity(
     getApplicationContext(), 
     0, 
     new Intent(),      //empty Intent do nothing
     Intent.FLAG_ACTIVITY_NEW_TASK);
   
   //generate notification
   String notificationText = String.valueOf((int)(100 * i / 10)) + " %";
   myNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle("Progress")
   .setContentText(notificationText)
   .setTicker("Notification!")
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .setContentIntent(pendingIntent_doNothing)
   .build();
   
   notificationManager.notify(MY_NOTIFICATION_ID, myNotification);
  }
  
  success = true;
  myNotification = new NotificationCompat.Builder(getApplicationContext())
  .setContentTitle("Success Finished")
  .setContentText("Successful Finished")
  .setTicker("Successful Finished")
  .setWhen(System.currentTimeMillis())
  .setDefaults(Notification.DEFAULT_SOUND)
  .setAutoCancel(true)
  .setSmallIcon(R.drawable.ic_launcher)
  .build();
  notificationManager.notify(MY_NOTIFICATION_ID, myNotification);
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

 @Override
 public void onCreate() {
  super.onCreate();
  notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
 }

 @Override
 public void onDestroy() {
  Notification onDestroyNotification;
  String notice;
  
  stopped = true;
  
  if(success){
   notice = "onDestroy with success";
   onDestroyNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle(notice)
   .setContentText(msgFromActivity)
   .setTicker(notice)
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .build();
  }else{
   notice = "onDestroy WITHOUT success!";
   onDestroyNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle(notice)
   .setContentText(msgFromActivity)
   .setTicker(notice)
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .build();
  }
  notificationManager.notify(MY_DESTORY_NOTIFICATION_ID, onDestroyNotification);
  Toast.makeText(getApplicationContext(), notice, Toast.LENGTH_LONG).show();
  
  super.onDestroy();
 }

}


Need to include <service> of "MyIntentService" in AndroidManifest.xml, refer to the post "Send data from IntentService to Activity, via additional Broadcast".


download filesDownload the files.

Create a PendingIntent for Notification to do nothing

Refer to the last post "error of using NotificationCompat.Builder, IllegalArgumentException: contentIntent required", if you want no action perform when user click on the Notification, you can insert a PendingIntent with a dummy Intent.

Example:


    PendingIntent pendingIntent = PendingIntent.getActivity(
      MainActivity.this, 
      0, 
      new Intent(),  //Dummy Intent do nothing 
      Intent.FLAG_ACTIVITY_NEW_TASK);
      
    myNotification = new NotificationCompat.Builder(context)
          .setContentTitle("Exercise of Notification!")
          .setContentText("http://android-er.blogspot.com/")
          .setTicker("Notification!")
          .setWhen(System.currentTimeMillis())
          .setContentIntent(pendingIntent)
          .setDefaults(Notification.DEFAULT_SOUND)
          .setAutoCancel(true)
          .setSmallIcon(R.drawable.ic_launcher)
          .build();



error of using NotificationCompat.Builder, IllegalArgumentException: contentIntent required

Refer to the exercise "Example of using NotificationCompat.Builder"; if the statement of new NotificationCompat.Builder...build() modified to remove .setContentIntent(pendingIntent), error of java.lang.IllegalArgumentException: contentIntent required MAY be thrown.

It will have no error in compile time, and no error when run on HTC One X (Android 4.1.1) and HTC Fly (Android 3.2.1). But error when run on Nexus One (Android 2.3.6), with error:

03-27 21:33:40.631: D/AndroidRuntime(24248): Shutting down VM
03-27 21:33:40.631: W/dalvikvm(24248): threadid=1: thread exiting with uncaught exception (group=0x40015560)
03-27 21:33:40.651: E/AndroidRuntime(24248): FATAL EXCEPTION: main
03-27 21:33:40.651: E/AndroidRuntime(24248): java.lang.IllegalArgumentException: contentIntent required: pkg=com.example.androidnotificationbuilder id=1 notification=Notification(vibrate=null,sound=default,defaults=0x1,flags=0x10)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.os.Parcel.readException(Parcel.java:1326)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.os.Parcel.readException(Parcel.java:1276)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.app.INotificationManager$Stub$Proxy.enqueueNotificationWithTag(INotificationManager.java:274)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.app.NotificationManager.notify(NotificationManager.java:111)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.app.NotificationManager.notify(NotificationManager.java:91)
03-27 21:33:40.651: E/AndroidRuntime(24248): at com.example.androidnotificationbuilder.MainActivity$1.onClick(MainActivity.java:52)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.view.View.performClick(View.java:2485)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.view.View$PerformClick.run(View.java:9080)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.os.Handler.handleCallback(Handler.java:587)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.os.Handler.dispatchMessage(Handler.java:92)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.os.Looper.loop(Looper.java:130)
03-27 21:33:40.651: E/AndroidRuntime(24248): at android.app.ActivityThread.main(ActivityThread.java:3683)
03-27 21:33:40.651: E/AndroidRuntime(24248): at java.lang.reflect.Method.invokeNative(Native Method)
03-27 21:33:40.651: E/AndroidRuntime(24248): at java.lang.reflect.Method.invoke(Method.java:507)
03-27 21:33:40.651: E/AndroidRuntime(24248): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
03-27 21:33:40.651: E/AndroidRuntime(24248): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
03-27 21:33:40.651: E/AndroidRuntime(24248): at dalvik.system.NativeStart.main(Native Method)

IllegalArgumentException: contentIntent required on Nexus One

Run on HTC One X

Run on HTC Flyer


If you want no action perform when user click on the Notification, you can create a PendingIntent with empty Intent for Notification to do nothing.


Tuesday, March 26, 2013

Generate Notification in IntentService

Last exercise demonstrate how to "Send data from IntentService to Activity, via additional Broadcast". We can also generate Notification in IntentService, to inform user about the progress.

Generate Notification in IntentService

Generate Notification in IntentService


Modify MyIntentService.java from last exercise.
package com.example.androidintentservice;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;

public class MyIntentService extends IntentService {
 
 private static final int MY_NOTIFICATION_ID=1;
 NotificationManager notificationManager;
 Notification myNotification;
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String ACTION_MyUpdate = "com.example.androidintentservice.UPDATE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 public static final String EXTRA_KEY_UPDATE = "EXTRA_UPDATE";
 String msgFromActivity;
 String extraOut;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  msgFromActivity = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Hello: " +  msgFromActivity;
  
  for(int i = 0; i <=10; i++){
   try {
    Thread.sleep(1000);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
   //send update 
   Intent intentUpdate = new Intent();
   intentUpdate.setAction(ACTION_MyUpdate);
   intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
   intentUpdate.putExtra(EXTRA_KEY_UPDATE, i);
   sendBroadcast(intentUpdate);
   
   //generate notification
   String notificationText = String.valueOf((int)(100 * i / 10)) + " %";
   myNotification = new NotificationCompat.Builder(getApplicationContext())
   .setContentTitle("Progress")
   .setContentText(notificationText)
   .setTicker("Notification!")
   .setWhen(System.currentTimeMillis())
   .setDefaults(Notification.DEFAULT_SOUND)
   .setAutoCancel(true)
   .setSmallIcon(R.drawable.ic_launcher)
   .build();
   
   notificationManager.notify(MY_NOTIFICATION_ID, myNotification);
  }
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

 @Override
 public void onCreate() {
  // TODO Auto-generated method stub
  super.onCreate();
  notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
 }

}


download filesDownload the files.

Related:
- error of using NotificationCompat.Builder, IllegalArgumentException: contentIntent required

Next:
- Stop IntentService, and know about the life-cycle of IntentService.

Monday, March 25, 2013

Send data from IntentService to Activity, via additional Broadcast

The former post demonstrate "Perform background processing with IntentService". It's a standard approach for IntentService, inform the Activity end of job by sending a Broadcast with Extra of respond.

But how can main Activity know the status of IntentService before end of job? It's another exercise to send additional Broadcast with another IntentFilter while running, to update main Activity, from IntentService. Such that we can implement additional BroadcastReceiver in main Activity to receive the additional Broadcast , and then update a ProgressBar.

Send data from IntentService to Activity, via additional Broadcast


MyIntentService.java
package com.example.androidintentservice;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String ACTION_MyUpdate = "com.example.androidintentservice.UPDATE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 public static final String EXTRA_KEY_UPDATE = "EXTRA_UPDATE";
 String msgFromActivity;
 String extraOut;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  msgFromActivity = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Hello: " +  msgFromActivity;
  
  for(int i = 0; i <=10; i++){
   try {
    Thread.sleep(1000);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
   //send update 
   Intent intentUpdate = new Intent();
   intentUpdate.setAction(ACTION_MyUpdate);
   intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
   intentUpdate.putExtra(EXTRA_KEY_UPDATE, i);
   sendBroadcast(intentUpdate);
  }
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

}


MainActivity.java
package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView textResult;
 ProgressBar progressBar;
 
 private MyBroadcastReceiver myBroadcastReceiver;
 private MyBroadcastReceiver_Update myBroadcastReceiver_Update;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textResult = (TextView)findViewById(R.id.result);
  progressBar = (ProgressBar)findViewById(R.id.progressbar);

  //prepare MyParcelable passing to intentMyIntentService
  String msgToIntentService = "Android-er";

  //Start MyIntentService
  Intent intentMyIntentService = new Intent(this, MyIntentService.class);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, msgToIntentService);
  startService(intentMyIntentService);
  
  myBroadcastReceiver = new MyBroadcastReceiver();
  myBroadcastReceiver_Update = new MyBroadcastReceiver_Update();
  
  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
  
  IntentFilter intentFilter_update = new IntentFilter(MyIntentService.ACTION_MyUpdate);
  intentFilter_update.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver_Update, intentFilter_update);
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
  unregisterReceiver(myBroadcastReceiver_Update);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }
 }
 
 public class MyBroadcastReceiver_Update extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   int update = intent.getIntExtra(MyIntentService.EXTRA_KEY_UPDATE, 0);
   progressBar.setProgress(update);
  }
 }

}


activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        style="?android:attr/progressBarStyleHorizontal"
        android:max="10"
        android:progress="0"/>

</LinearLayout>


Need to include <service> of "MyIntentService" in AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidintentservice"
    android:versionCode="1"
    android:versionName="1.0" >

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

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="com.example.androidintentservice.MyIntentService"></service>
    </application>

</manifest>



download filesDownload the files.

Related:
- Generate Notification in IntentService


Example of using NotificationCompat.Builder

Last exercise demonstrate "Notification.Builder". If your app supports versions of Android as old as API level 4, you can instead use NotificationCompat.Builder, available in the Android Support library.

Example of using NotificationCompat.Builder


Simple replace Notification.Builder with NotificationCompat.Builder, and import android.support.v4.app.NotificationCompat.



    myNotification = new NotificationCompat.Builder(context)
          .setContentTitle("Exercise of Notification!")
          .setContentText("http://android-er.blogspot.com/")
          .setTicker("Notification!")
          .setWhen(System.currentTimeMillis())
          .setContentIntent(pendingIntent)
          .setDefaults(Notification.DEFAULT_SOUND)
          .setAutoCancel(true)
          .setSmallIcon(R.drawable.ic_launcher)
          .build();



Related:
- error of using NotificationCompat.Builder, IllegalArgumentException: contentIntent required


Example of using Notification.Builder

android.app.Notification.Builder is a builder class for Notification objects. Provides a convenient way to set the various fields of a Notification and generate content views using the platform's notification layout template. If your app supports versions of Android as old as API level 4, you can instead use NotificationCompat.Builder, available in the Android Support library.

Example of using Notification.Builder

Example of using Notification.Builder


package com.example.androidnotificationbuilder;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
 
 private static final int MY_NOTIFICATION_ID=1;
 NotificationManager notificationManager;
 Notification myNotification;
 private final String myBlog = "http://android-er.blogspot.com/";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Button buttonSend = (Button)findViewById(R.id.send);
  buttonSend.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    Context context = getApplicationContext();
    Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(myBlog));
    PendingIntent pendingIntent = PendingIntent.getActivity(
      MainActivity.this, 
      0, 
      myIntent, 
      Intent.FLAG_ACTIVITY_NEW_TASK);
      
    myNotification = new Notification.Builder(context)
          .setContentTitle("Exercise of Notification!")
          .setContentText("http://android-er.blogspot.com/")
          .setTicker("Notification!")
          .setWhen(System.currentTimeMillis())
          .setContentIntent(pendingIntent)
          .setDefaults(Notification.DEFAULT_SOUND)
          .setAutoCancel(true)
          .setSmallIcon(R.drawable.ic_launcher)
          .build();
    
    notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(MY_NOTIFICATION_ID, myNotification);

   }});

 }

}



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/send"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Send a Notification" />

</LinearLayout>


Note: minSdkVersion have to be set ="16".

Friday, March 22, 2013

Share IntentService among Fragments

The post "Perform background processing with IntentService" demonstrate how to use IntentService in Activity. In this exercise, we are going to modify the exercise of "Yahoo Weather" to demonstrate how to share one IntentService by three Fragments. Here one IntentService means one common IntentService class, not one common IntentService object.




Create MyIntentService.java, it's our common IntentService class, to load Yahoo Weather in background service.
package com.example.androidyahooweatherdom;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {
 
 String ExtraAction;
 String woeid;
 MyWeather weatherResult;
 
 
 public static final String EXTRA_KEY_ACTION = "EXTRA_ACTION";
 public static final String EXTRA_KEY_WOEID = "EXTRA_WOEID";
 public static final String EXTRA_KEY_WEATHER = "EXTRA_WEATHER";
 
 class MyWeather{
  String description;
  String city;
  String region;
  String country;

  String windChill;
  String windDirection;
  String windSpeed;

  String sunrise;
  String sunset;

  String conditiontext;
  String conditiondate;
  
  String numberOfForecast;
  String forecast;

  public String toString(){
   
   return "\n- " + description + " -\n\n"
    + "city: " + city + "\n"
    + "region: " + region + "\n"
    + "country: " + country + "\n\n"

    + "Wind\n"
    + "chill: " + windChill + "\n"
    + "direction: " + windDirection + "\n"
    + "speed: " + windSpeed + "\n\n"

    + "Sunrise: " + sunrise + "\n"
    + "Sunset: " + sunset + "\n\n"

    + "Condition: " + conditiontext + "\n"
    + conditiondate +"\n"
    
    + "\n"
    + "number of forecast: " + numberOfForecast + "\n"
    + forecast;
   
  } 
 }

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  ExtraAction = intent.getStringExtra(EXTRA_KEY_ACTION);
  woeid = intent.getStringExtra(EXTRA_KEY_WOEID);
  
  loadYahooWeather();
  
  //dummy delay
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ExtraAction);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_WEATHER, weatherResult.toString());
  sendBroadcast(intentResponse);
 }
 
 protected void loadYahooWeather(){
  
  String weatherString = QueryYahooWeather();
  Document weatherDoc = convertStringToDocument(weatherString);
  weatherResult = parseWeather(weatherDoc);
 }

 private MyWeather parseWeather(Document srcDoc){
     
     MyWeather myWeather = new MyWeather();

     //<description>Yahoo! Weather for New York, NY</description>
     myWeather.description = srcDoc.getElementsByTagName("description")
       .item(0)
       .getTextContent();

     //<yweather:location.../>
     Node locationNode = srcDoc.getElementsByTagName("yweather:location").item(0);
     myWeather.city = locationNode.getAttributes()
       .getNamedItem("city")
       .getNodeValue()
       .toString();
     myWeather.region = locationNode.getAttributes()
       .getNamedItem("region")
       .getNodeValue()
       .toString();
     myWeather.country = locationNode.getAttributes()
       .getNamedItem("country")
       .getNodeValue()
       .toString();

     //<yweather:wind.../>
     Node windNode = srcDoc.getElementsByTagName("yweather:wind").item(0);
     myWeather.windChill = windNode.getAttributes()
       .getNamedItem("chill")
       .getNodeValue()
       .toString();
     myWeather.windDirection = windNode.getAttributes()
       .getNamedItem("direction")
       .getNodeValue()
       .toString();
     myWeather.windSpeed = windNode.getAttributes()
       .getNamedItem("speed")
       .getNodeValue()
       .toString();

     //<yweather:astronomy.../>
     Node astronomyNode = srcDoc.getElementsByTagName("yweather:astronomy").item(0);
     myWeather.sunrise = astronomyNode.getAttributes()
       .getNamedItem("sunrise")
       .getNodeValue()
       .toString();
     myWeather.sunset = astronomyNode.getAttributes()
       .getNamedItem("sunset")
       .getNodeValue()
       .toString();

     //<yweather:condition.../>
     Node conditionNode = srcDoc.getElementsByTagName("yweather:condition").item(0);
     myWeather.conditiontext = conditionNode.getAttributes()
       .getNamedItem("text")
       .getNodeValue()
       .toString();
     myWeather.conditiondate = conditionNode.getAttributes()
       .getNamedItem("date")
       .getNodeValue()
       .toString();
     
     //Added to get elements of <yweather:forecast.../>
     NodeList forecastList = srcDoc.getElementsByTagName("yweather:forecast");
     
     myWeather.forecast = "";
     if(forecastList.getLength() > 0){
      myWeather.numberOfForecast = String.valueOf(forecastList.getLength());
      for(int i = 0; i < forecastList.getLength(); i++){
       Node forecastNode = forecastList.item(i);
       myWeather.forecast +=
         forecastNode
          .getAttributes()
          .getNamedItem("date")
          .getNodeValue()
          .toString() + " " +
         forecastNode
          .getAttributes()
          .getNamedItem("text")
          .getNodeValue()
          .toString() +
         " High: " + forecastNode
             .getAttributes()
             .getNamedItem("high")
             .getNodeValue()
             .toString() +
         " Low: " + forecastNode
             .getAttributes()
             .getNamedItem("low")
             .getNodeValue()
             .toString() + "\n";
      }
     }else{
      myWeather.numberOfForecast = "No forecast";
     }
     
     return myWeather; 
    }
    
    private Document convertStringToDocument(String src){
     
     Document dest = null;
     DocumentBuilderFactory dbFactory =
       DocumentBuilderFactory.newInstance();
     DocumentBuilder parser;
     
     try {
      parser = dbFactory.newDocumentBuilder();
      dest = parser.parse(new ByteArrayInputStream(src.getBytes())); 
     } catch (ParserConfigurationException e1) {
      e1.printStackTrace(); 
     } catch (SAXException e) {
      e.printStackTrace(); 
     } catch (IOException e) {
      e.printStackTrace(); 
     }
     
     return dest; 
    }

    private String QueryYahooWeather(){
     
     String qResult = "";
     String queryString = "http://weather.yahooapis.com/forecastrss?w=" + woeid;
     
     HttpClient httpClient = new DefaultHttpClient();
     HttpGet httpGet = new HttpGet(queryString);
       
     try {
      HttpEntity httpEntity = httpClient.execute(httpGet).getEntity();
      
      if (httpEntity != null){
       InputStream inputStream = httpEntity.getContent();
       Reader in = new InputStreamReader(inputStream);
       BufferedReader bufferedreader = new BufferedReader(in);
       StringBuilder stringBuilder = new StringBuilder();
          
       String stringReadLine = null;

       while ((stringReadLine = bufferedreader.readLine()) != null) {
        stringBuilder.append(stringReadLine + "\n"); 
       }
          
       qResult = stringBuilder.toString(); 
      } 
     } catch (ClientProtocolException e) {
      e.printStackTrace(); 
     } catch (IOException e) {
      e.printStackTrace();
     }
     
     return qResult; 
    }

}


Modify AndroidManifest.xml to add <service> of "MyIntentService", and add permission of "android.permission.INTERNET".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidyahooweatherdom"
    android:versionCode="1"
    android:versionName="1.0" >

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

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

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

</manifest>


res/layout/fragmentlayout.xml, layout of our fragments.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold" />
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TextView
            android:id="@+id/weather"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>


Creater a abstract class MyAbsYWeatherFragment extends Fragment, all our fragments will extend it. All it's sub-class have to override init_country() method to provide the target city of Yahoo Weather, a string of ACTION_RESPONSE for IntentFilter, and boolean retainInst to determine is it need retain instance. It will start our IntentService and handle all job for our fragments.
package com.example.androidyahooweatherdom;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public abstract class MyAbsYWeatherFragment extends Fragment {

 TextView weather, status;
 String stringWeatherResult;

 private MyBroadcastReceiver myBroadcastReceiver;
 
 String woeid;
 String ACTION_RESPONSE;
 boolean retainInst;
 abstract void init_country();
 
 public MyAbsYWeatherFragment(){
  super();
  init_country();
 }
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  View myFragmentView = inflater.inflate(R.layout.fragmentlayout, container, false);
  weather = (TextView)myFragmentView.findViewById(R.id.weather);
        status = (TextView)myFragmentView.findViewById(R.id.status);
        
        myBroadcastReceiver = new MyBroadcastReceiver();
        
        //register BroadcastReceiver
        IntentFilter intentFilter = new IntentFilter(ACTION_RESPONSE);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        getActivity().registerReceiver(myBroadcastReceiver, intentFilter);
        
  return myFragmentView;
 }

 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onActivityCreated(savedInstanceState);
  
  if(stringWeatherResult != null){
   weather.setText(stringWeatherResult);
   status.setText("Reloaded previous weatherResult");
  }else{
   status.setText("loadYahooWeather()");
   startServiceToLoadYahooWeather();
  }
  
  setRetainInstance(retainInst);
 }
 
 @Override
 public void onDestroyView() {
  // TODO Auto-generated method stub
  super.onDestroyView();
  //un-register BroadcastReceiver
  getActivity().unregisterReceiver(myBroadcastReceiver);
 }

 protected void startServiceToLoadYahooWeather(){
  Intent intentMyIntentService = new Intent(getActivity().getApplicationContext(), MyIntentService.class);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_ACTION, ACTION_RESPONSE);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_WOEID, woeid);
  getActivity().startService(intentMyIntentService);
 }
 
 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   stringWeatherResult = intent.getStringExtra(MyIntentService.EXTRA_KEY_WEATHER);

   FragmentActivity parentActivity = getActivity();
   if(parentActivity != null){
       parentActivity.runOnUiThread(new Runnable(){
           
           @Override
           public void run() {
            weather.setText(stringWeatherResult);
            status.setText("Finished"); 
           }});
      }
  }

 }

}


Create three sub-class of MyAbsYWeatherFragment, Fragment1, Fragment2 and Fragment3. They are the actual fragments in the layout.

Fragment1.java
package com.example.androidyahooweatherdom;

public class Fragment1 extends MyAbsYWeatherFragment {

 @Override
 void init_country() {
  //New York
  woeid = "2459115";
  ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.NewYork";
  retainInst = false;
 }
 
}


Fragment2.java
package com.example.androidyahooweatherdom;

public class Fragment2 extends MyAbsYWeatherFragment {

 @Override
 void init_country() {
  //New York
  woeid = "2459115";
  ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.NewYork";
  retainInst = true;
 }

}


Fragment3.java
package com.example.androidyahooweatherdom;

public class Fragment3 extends MyAbsYWeatherFragment {

 @Override
 void init_country() {
  //London
  woeid = "23416974";
  ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.London";
  retainInst = true;
 }

}


Modify /res/layout/activity_main.xml and /res/layout-land/activity_main.xml to add the three fragments in normal and landscape orientation.

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <fragment
        class="com.example.androidyahooweatherdom.Fragment1"
        android:id="@+id/fragment1"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment2"
        android:id="@+id/fragment2"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment3"
        android:id="@+id/fragment3"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1" />

</LinearLayout>


/res/layout-land/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <fragment
        class="com.example.androidyahooweatherdom.Fragment1"
        android:id="@+id/fragment1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment2"
        android:id="@+id/fragment2"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment3"
        android:id="@+id/fragment3"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    
</LinearLayout>


MainActivity
package com.example.androidyahooweatherdom;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {

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

}


- Fragment1 call setRetainInstance(flase), so everytime orientation changed, it will re-start and reload new weather.

- Fragment3 call setRetainInstance(true), so it will not reload weather.

- Fragment2 call setRetainInstance(true), it will not reload weather too. But it have the same Action string for IntentFilter of Fragment1, so when IntentService of Fragment1 finished, BroadcastReceiver of Fragment2 will be called also.



download filesDownload the files.

If you have RuntimeException: Unable to instantiate activity ComponentInfo, please read HERE.

Thursday, March 21, 2013

Example to implement Parcelable

Parcel is a class container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general Parcelable interface), and references to live IBinder objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.

Parcelable is Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface.

Modify last exercise "Perform background processing with IntentService" to pass extra as our custom Parcelable.

Extra passed as Parcelable


Create our custom class MyParcelable.java, implements Parcelable.
package com.example.androidintentservice;

import android.os.Parcel;
import android.os.Parcelable;

public class MyParcelable implements Parcelable {
 
 public String blogName;
 public String blogAddress;
 
 public static final Parcelable.Creator<MyParcelable> CREATOR =
   new Parcelable.Creator<MyParcelable>(){

    @Override
    public MyParcelable createFromParcel(Parcel source) {
     return new MyParcelable(source);
    }

    @Override
    public MyParcelable[] newArray(int size) {
     return new MyParcelable[size];
    }
 };
 
 public MyParcelable(){
 }
 
 public MyParcelable(Parcel source){
  readFromParcel(source);
 }

 @Override
 public int describeContents() {
  return 0;
 }

 @Override
 public void writeToParcel(Parcel dest, int flags) {
  dest.writeString(blogName);
  dest.writeString(blogAddress);
 }
 
 public void readFromParcel(Parcel source){
  blogName = source.readString();
  blogAddress = source.readString();
 }

}


Modify main activity, MainActivity.java to pass Extra as MyParcelable.
package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView textResult;
 
 private MyBroadcastReceiver myBroadcastReceiver;

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

  //prepare MyParcelable passing to intentMyIntentService
  MyParcelable myParcelable = new MyParcelable();
  myParcelable.blogName = "Android-er";
  myParcelable.blogAddress = "http://android-er.blogspot.com/";
  
  //Start MyIntentService
  Intent intentMyIntentService = new Intent(this, MyIntentService.class);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, myParcelable);
  startService(intentMyIntentService);
  
  myBroadcastReceiver = new MyBroadcastReceiver();
  
  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }

 }

}


Modify MyIntentService.java to receive MyIntentService.
package com.example.androidintentservice;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 MyParcelable parcelableIn;
 String extraOut;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  parcelableIn = intent.getParcelableExtra(EXTRA_KEY_IN);
  extraOut = "Passed as Parcelable:\n" 
    + parcelableIn.blogName + "\n" 
    + parcelableIn.blogAddress;
  
  //dummy delay for 5 sec
  try {
   Thread.sleep(5000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } //wait 3 sec
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

}


download filesDownload the files.

Perform background processing with IntentService

android.app.IntentService is a subclass of android.app.Service that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

IntentService


Create our custom IntentService, MyIntentService.java

You have to implement constructor of MyIntentService(). If you only override the default constructor of MyIntentService(String name) from IntentService, RuntimeException of java.lang.InstantiationException with Unable to instantiate service will be thrown.

package com.example.androidintentservice;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {
 
 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 String extraIn;
 String extraOut;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {
  
  //get input
  extraIn = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Result from MyIntentService: Hello " + extraIn;
  
  //dummy delay for 5 sec
  try {
   Thread.sleep(5000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } //wait 3 sec
  
  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

}


Modify layout to add a TextView to display result
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

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

</LinearLayout>


Main code in MainActivity.java. In order to receive result from IntentService, implement BroadcastReceiver, register in onCreate() and unregister in onDestroy(). To start MyIntentService, call startService() with coresponding intent.
package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView textResult;
 
 private MyBroadcastReceiver myBroadcastReceiver;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textResult = (TextView)findViewById(R.id.result);
  
  //Start MyIntentService
  Intent intentMyIntentService = new Intent(this, MyIntentService.class);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, "Android-er");
  startService(intentMyIntentService);
  
  myBroadcastReceiver = new MyBroadcastReceiver();
  
  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }

 }

}


Modify AndroidManifest.xml to add <service> of MyIntentService
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidintentservice"
    android:versionCode="1"
    android:versionName="1.0" >

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

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="com.example.androidintentservice.MyIntentService"></service>
    </application>

</manifest>


download filesDownload the files.

Next:
- Share IntentService among Fragments
- Send data from IntentService to Activity, via additional Broadcast
- Generate Notification in IntentService