Sunday, November 27, 2011

Implement Pinch Zoom in OnTouchListener

Modify the last exercise of "Implement OnTouchListener to handle multi-touch event" to implement our own pinch detection.

Implement Pinch Zoom in OnTouchListener

package com.exercise.AndroidTouchPinchZoom;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.TextView;

public class AndroidTouchPinchZoomActivity extends Activity {

TextView myTouchEvent;
ImageView myImageView;
Bitmap bitmap;
int bmpWidth, bmpHeight;

//Touch event related variables
int touchState;
final int IDLE = 0;
final int TOUCH = 1;
final int PINCH = 2;
float dist0, distCurrent;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myTouchEvent = (TextView)findViewById(R.id.touchevent);
myImageView = (ImageView)findViewById(R.id.imageview);

bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
bmpWidth = bitmap.getWidth();
bmpHeight = bitmap.getHeight();

distCurrent = 1; //Dummy default distance
dist0 = 1; //Dummy default distance
drawMatrix();

myImageView.setOnTouchListener(MyOnTouchListener);
touchState = IDLE;
}

private void drawMatrix(){
float curScale = distCurrent/dist0;
if (curScale < 0.1){
curScale = 0.1f;
}

Bitmap resizedBitmap;
int newHeight = (int) (bmpHeight * curScale);
int newWidth = (int) (bmpWidth * curScale);
resizedBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
myImageView.setImageBitmap(resizedBitmap);
}

OnTouchListener MyOnTouchListener
= new OnTouchListener(){

@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub

float distx, disty;

switch(event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
//A pressed gesture has started, the motion contains the initial starting location.
myTouchEvent.setText("ACTION_DOWN");
touchState = TOUCH;
break;
case MotionEvent.ACTION_POINTER_DOWN:
//A non-primary pointer has gone down.
myTouchEvent.setText("ACTION_POINTER_DOWN");
touchState = PINCH;

//Get the distance when the second pointer touch
distx = event.getX(0) - event.getX(1);
disty = event.getY(0) - event.getY(1);
dist0 = FloatMath.sqrt(distx * distx + disty * disty);

break;
case MotionEvent.ACTION_MOVE:
//A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
myTouchEvent.setText("ACTION_MOVE");

if(touchState == PINCH){
//Get the current distance
distx = event.getX(0) - event.getX(1);
disty = event.getY(0) - event.getY(1);
distCurrent = FloatMath.sqrt(distx * distx + disty * disty);

drawMatrix();
}

break;
case MotionEvent.ACTION_UP:
//A pressed gesture has finished.
myTouchEvent.setText("ACTION_UP");
touchState = IDLE;
break;
case MotionEvent.ACTION_POINTER_UP:
//A non-primary pointer has gone up.
myTouchEvent.setText("ACTION_POINTER_UP");
touchState = TOUCH;
break;
}

return true;
}

};
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:id="@+id/touchevent"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/imageview"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center" />

</LinearLayout>


Download the files.


Related article:
- Scale bitmap according to ScaleGestureDetector



Saturday, November 26, 2011

Implement OnTouchListener to handle multi-touch event

In this exercise, we will implement our OnTouchListener to handle the following MotionEvent:
ACTION_DOWN: A pressed gesture has started, the motion contains the initial starting location.
ACTION_POINTER_DOWN: A non-primary pointer has gone down.
ACTION_MOVE: A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
ACTION_UP: A pressed gesture has finished.
ACTION_POINTER_UP: A non-primary pointer has gone up.

It will be used to implement our pinch zoom in coming exercise.

Implement OnTouchListener to handle multi-touch event

package com.exercise.AndroidTouchPinchZoom;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.TextView;

public class AndroidTouchPinchZoomActivity extends Activity {

TextView myTouchEvent;
ImageView myImageView;
Bitmap bitmap;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myTouchEvent = (TextView)findViewById(R.id.touchevent);
myImageView = (ImageView)findViewById(R.id.imageview);

bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
myImageView.setImageBitmap(bitmap);

myImageView.setOnTouchListener(MyOnTouchListener);
}

OnTouchListener MyOnTouchListener
= new OnTouchListener(){

@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub

switch(event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
//A pressed gesture has started, the motion contains the initial starting location.
myTouchEvent.setText("ACTION_DOWN");
break;
case MotionEvent.ACTION_POINTER_DOWN:
//A non-primary pointer has gone down.
myTouchEvent.setText("ACTION_POINTER_DOWN");
break;
case MotionEvent.ACTION_MOVE:
//A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
myTouchEvent.setText("ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
//A pressed gesture has finished.
myTouchEvent.setText("ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_UP:
//A non-primary pointer has gone up.
myTouchEvent.setText("ACTION_POINTER_UP");
break;
}

return true;
}

};
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:id="@+id/touchevent"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/imageview"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center" />

</LinearLayout>


Download the files.

Thursday, November 24, 2011

Scale bitmap according to ScaleGestureDetector

In the last exercise, "Detect pinch zoom using ScaleGestureDetector" was shown. it's modified to scale a bitmap accordingly. (It's for demonstration only, may be not practical in real use.

Scale bitmap according to ScaleGestureDetector

package com.exercise.AndroidPinchZoom;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import android.widget.ImageView;
import android.widget.TextView;

public class AndroidPinchZoomActivity extends Activity {

TextView scaleGesture;
ImageView myImageView;
float curScale = 1F;
Bitmap bitmap;
int bmpWidth, bmpHeight;

ScaleGestureDetector scaleGestureDetector;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
scaleGesture = (TextView)findViewById(R.id.ScaleGesture);
myImageView = (ImageView)findViewById(R.id.imageview);

bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
bmpWidth = bitmap.getWidth();
bmpHeight = bitmap.getHeight();
drawMatrix();

scaleGestureDetector = new ScaleGestureDetector(this, new simpleOnScaleGestureListener());
}

private void drawMatrix(){

curScale = ((curScale - 1) * 10) + 1;
if (curScale < 0.1){
curScale = 0.1f;
}

Bitmap resizedBitmap;
int newHeight = (int) (bmpHeight * curScale);
int newWidth = (int) (bmpWidth * curScale);
resizedBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
myImageView.setImageBitmap(resizedBitmap);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
scaleGestureDetector.onTouchEvent(event);
return true;
}

public class simpleOnScaleGestureListener extends SimpleOnScaleGestureListener {

@Override
public boolean onScale(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
curScale = detector.getScaleFactor();
scaleGesture.setText(String.valueOf(curScale));
drawMatrix();
return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
super.onScaleEnd(detector);
}

}
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:id="@+id/ScaleGesture"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/imageview"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center" />
</LinearLayout>


Download the files.

Related article:
- Implement Pinch Zoom in OnTouchListener



Wednesday, November 23, 2011

Detect pinch zoom using ScaleGestureDetector

For Android 2.2 (Froyo) android.view.ScaleGestureDetector was added for processing the most commonly requested two-finger gesture: pinch zooming.

Detect pinch zoom using ScaleGestureDetector

package com.exercise.AndroidScaleGestureDetector;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import android.view.View;
import android.widget.TextView;

public class AndroidScaleGestureDetectorActivity extends Activity {

TextView scaleGesture;
ScaleGestureDetector scaleGestureDetector;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
scaleGesture = (TextView)findViewById(R.id.ScaleGesture);

scaleGestureDetector = new ScaleGestureDetector(this, new simpleOnScaleGestureListener());
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
scaleGestureDetector.onTouchEvent(event);
return true;
}

public class simpleOnScaleGestureListener extends
SimpleOnScaleGestureListener {

@Override
public boolean onScale(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
scaleGesture.setText(String.valueOf(detector.getScaleFactor()));
return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
scaleGesture.setVisibility(View.VISIBLE);
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// TODO Auto-generated method stub
scaleGesture.setVisibility(View.INVISIBLE);
}

}
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:id="@+id/ScaleGesture"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

</LinearLayout>


Download the files.

next:
- Scale bitmap according to ScaleGestureDetector



Thursday, November 17, 2011

Detect swipe using SimpleOnGestureListener

Last exercise demonstrate how to "Using GestureDetector with SimpleOnGestureListener". In this exercise, we modify the SimpleOnGestureListener to detect swipe by overriding onFling() method.

Detect swipe using SimpleOnGestureListener

package com.exercise.AndroidSimpleGesture;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.widget.TextView;

public class AndroidSimpleGestureActivity extends Activity {

TextView gestureEvent;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gestureEvent = (TextView)findViewById(R.id.GestureEvent);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return gestureDetector.onTouchEvent(event);
}

SimpleOnGestureListener simpleOnGestureListener
= new SimpleOnGestureListener(){


@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
String swipe = "";
float sensitvity = 50;

// TODO Auto-generated method stub
if((e1.getX() - e2.getX()) > sensitvity){
swipe += "Swipe Left\n";
}else if((e2.getX() - e1.getX()) > sensitvity){
swipe += "Swipe Right\n";
}else{
swipe += "\n";
}

if((e1.getY() - e2.getY()) > sensitvity){
swipe += "Swipe Up\n";
}else if((e2.getY() - e1.getY()) > sensitvity){
swipe += "Swipe Down\n";
}else{
swipe += "\n";
}

gestureEvent.setText(swipe);

return super.onFling(e1, e2, velocityX, velocityY);
}
};

GestureDetector gestureDetector
= new GestureDetector(simpleOnGestureListener);
}



Download the files.

Using GestureDetector with SimpleOnGestureListener

GestureDetector.SimpleOnGestureListener is a convenience class to extend when you only want to listen for a subset of all the gestures.

Using GestureDetector with SimpleOnGestureListener

package com.exercise.AndroidSimpleGesture;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.widget.TextView;

public class AndroidSimpleGestureActivity extends Activity {

TextView gestureEvent;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gestureEvent = (TextView)findViewById(R.id.GestureEvent);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return gestureDetector.onTouchEvent(event);
}

SimpleOnGestureListener simpleOnGestureListener
= new SimpleOnGestureListener(){

@Override
public boolean onDoubleTap(MotionEvent e) {
// TODO Auto-generated method stub
gestureEvent.setText("onDoubleTap: \n" + e.toString());
return super.onDoubleTap(e);
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
gestureEvent.setText("onFling: \n" + e1.toString() + "\n" + e2.toString() +"\n"
+ "velocityX= " + String.valueOf(velocityX) + "\n"
+ "velocityY= " + String.valueOf(velocityY) + "\n");
return super.onFling(e1, e2, velocityX, velocityY);
}

@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
gestureEvent.setText("onLongPress: \n" + e.toString());
super.onLongPress(e);
}

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO Auto-generated method stub
gestureEvent.setText("onSingleTapConfirmed: \n" + e.toString());
return super.onSingleTapConfirmed(e);
}

};

GestureDetector gestureDetector
= new GestureDetector(simpleOnGestureListener);
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />

<TextView
android:id="@+id/GestureEvent"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

</LinearLayout>


Download the files.

next:
- Detect swipe using SimpleOnGestureListener



Saturday, November 12, 2011

NDK updated for Android 4.0

Google released an updated version of the Android NDK, now in revision 7. The updated NDK lets developers who are using native code get started with the new native APIs available in Android 4.0.

details: Updated NDK for Android 4.0

Tuesday, November 8, 2011

Cancel ProgressDialog

In the exercise "Display a indeterminate progress bar on title bar", the ProgressDialog will be dismissed by BackgroundThread after a certain time. How can user stop the waiting and force the BackgroundThread stop? we can set the ProgressDialog cancelable by calling progressDialog.setCancelable(true).

Cancel ProgressDialog

It's modified to be cancelable, such that user can cancel the waiting by BACK key. And a OnCancelListener is implemented to stop BackgroundThread. Also, a OnDismissListener is implemented. OnCancelListener will be called only when the dialog is canceled, if you needs to know when it is dismissed in general, use OnDismissListener.

package com.exercise.AndroidProgressDialog;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Window;
import android.widget.Toast;

public class AndroidProgressDialogActivity extends Activity {

ProgressDialog progressDialog;
BackgroundThread backgroundThread;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);

setProgressBarIndeterminateVisibility(true);

progressDialog = ProgressDialog.show(AndroidProgressDialogActivity.this,
"ProgressDialog", "Wait!");

backgroundThread = new BackgroundThread();
backgroundThread.setRunning(true);
backgroundThread.start();

progressDialog.setCancelable(true);

progressDialog.setOnCancelListener(new OnCancelListener(){

public void onCancel(DialogInterface dialog) {
// TODO Auto-generated method stub
backgroundThread.setRunning(false);
Toast.makeText(AndroidProgressDialogActivity.this,
"ProgressDialog Cancelled!",
Toast.LENGTH_LONG).show();
}});

progressDialog.setOnDismissListener(new OnDismissListener(){

public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
Toast.makeText(AndroidProgressDialogActivity.this,
"ProgressDialog Dismissed!",
Toast.LENGTH_LONG).show();
}});
}



public class BackgroundThread extends Thread{
volatile boolean running = false;
int cnt;

void setRunning(boolean b){
running = b;
cnt = 10;
}

@Override
public void run() {
// TODO Auto-generated method stub
while(running){
try {
sleep(1000);
if(cnt-- == 0){
running = false;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
handler.sendMessage(handler.obtainMessage());
}
}

Handler handler = new Handler(){

@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub

setProgressBarIndeterminateVisibility(false);
progressDialog.dismiss();

boolean retry = true;
while(retry){
try {
backgroundThread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Toast.makeText(AndroidProgressDialogActivity.this,
"dismissed", Toast.LENGTH_LONG).show();
}

};
}

Monday, November 7, 2011

More on onSaveInstanceState() and onRestoreInstanceState()

In the article onSaveInstanceState() and onRestoreInstanceState() described how to save and restore state in onSaveInstanceState() and onRestoreInstanceState() methods. It's another exercise, we can know more of life cycle of a activity about onSaveInstanceState() and onRestoreInstanceState().

onSaveInstanceState() and onRestoreInstanceState()

package com.exercise.AndroidSaveState;

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

public class AndroidSaveStateActivity extends Activity {

TextView textOnPause;
TextView textOnResume;
TextView textOnRestoreInstanceState;
TextView textOnSaveInstanceState;

int cntOnPause;
int cntOnResume;
int cntOnRestoreInstanceState;
int cntOnSaveInstanceState;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

textOnPause = (TextView)findViewById(R.id.onPause);
textOnResume = (TextView)findViewById(R.id.onResume);
textOnRestoreInstanceState = (TextView)findViewById(R.id.onRestoreInstanceState);
textOnSaveInstanceState = (TextView)findViewById(R.id.onSaveInstanceState);

}

@Override
protected void onPause() {
// TODO Auto-generated method stub
cntOnPause++;
super.onPause();
}


@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();

cntOnResume++;
textOnPause.setText("cntOnPause: " + String.valueOf(cntOnPause));
textOnResume.setText("cntOnResume: " + String.valueOf(cntOnResume));
textOnRestoreInstanceState.setText("cntOnRestoreInstanceState: " + String.valueOf(cntOnRestoreInstanceState));
textOnSaveInstanceState.setText("cntOnSaveInstanceState: " + String.valueOf(cntOnSaveInstanceState));
}

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
cntOnSaveInstanceState++;
outState.putInt("CNT_OnPause", cntOnPause);
outState.putInt("CNT_OnResume", cntOnResume);
outState.putInt("CNT_OnRestoreInstanceState", cntOnRestoreInstanceState);
outState.putInt("CNT_OnSaveInstanceState", cntOnSaveInstanceState);


super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub

if (savedInstanceState != null){
if(savedInstanceState.containsKey("CNT_OnPause")){
cntOnPause = savedInstanceState.getInt("CNT_OnPause");
}

if(savedInstanceState.containsKey("CNT_OnResume")){
cntOnResume = savedInstanceState.getInt("CNT_OnResume");
}

if(savedInstanceState.containsKey("CNT_OnRestoreInstanceState")){
cntOnRestoreInstanceState = savedInstanceState.getInt("CNT_OnRestoreInstanceState");
}

if(savedInstanceState.containsKey("CNT_OnSaveInstanceState")){
cntOnSaveInstanceState = savedInstanceState.getInt("CNT_OnSaveInstanceState");
}
}
cntOnRestoreInstanceState++;

super.onRestoreInstanceState(savedInstanceState);
}

}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />

<TextView
android:id="@+id/onPause"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/onResume"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/onRestoreInstanceState"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/onSaveInstanceState"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>

</LinearLayout>

Thursday, November 3, 2011

No applications can perform this action - Check Intent available and force to install

In the article "Send email using Intent.ACTION_SEND", we startActivity with intent of Intent.ACTION_SEND. I assume all Android should have gmail, or Composer, installed. But somebody complain with error of "No applications can perform this action".

We can check if any package installed to handle the intent in advance by calling queryIntentActivities(). If the returned list not ">0" (no aplications installed), open Market to install "market://details?id=com.google.android.gm".

Open Market if No applications can perform this action

Such a approach can be applied to other intent.

package com.exercise.AndroidEMail;

import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class AndroidEMail extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

final EditText edittextEmailAddress = (EditText)findViewById(R.id.email_address);
final EditText edittextEmailSubject = (EditText)findViewById(R.id.email_subject);
final EditText edittextEmailText = (EditText)findViewById(R.id.email_text);
Button buttonSendEmail_intent = (Button)findViewById(R.id.sendemail_intent);

//Preset default
edittextEmailAddress.setText("---@gmail.com");
edittextEmailSubject.setText("test");
edittextEmailText.setText("email body");

buttonSendEmail_intent.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub

String emailAddress = edittextEmailAddress.getText().toString();
String emailSubject = edittextEmailSubject.getText().toString();
String emailText = edittextEmailText.getText().toString();

String emailAddressList[] = {emailAddress};

Intent intent = new Intent(Intent.ACTION_SEND);

intent.setType("plain/text");
intent.putExtra(Intent.EXTRA_EMAIL, emailAddressList);
intent.putExtra(Intent.EXTRA_SUBJECT, emailSubject);
intent.putExtra(Intent.EXTRA_TEXT, emailText);

//Check if Intent available
List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() >0 ){
startActivity(Intent.createChooser(intent, "Choice App to send email:"));
}else{
//Start Market to downoad and install gmail
Uri uri = Uri.parse("market://details?id=com.google.android.gm");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
}

}});
}
}


The layout, main.xml, refer to the article "Send email using Intent.ACTION_SEND".

Download the files.

Wednesday, November 2, 2011

Old AdWords API with be deprecated

AdWords API blog just posted of "120 days until deprecation deadline".

With the release of AdWords API v201109, the following versions and services will be deprecated on February 29, 2012:
  • API versions v13, v200909, v201003, v201008, v201101
  • API version v13 AccountService will only be available on a whitelist basis