Search This Blog

Saturday, January 30, 2010

Android WebView

Android WebView enables us to display web pages and interact with web content, there are several reasons that you might want to use WebView in your applications, but i think one the most interesting features of WebView is JavaScript integration capability. in this article I'm gonna show you a simple example of how easy it is to call javascript functions from your java code and vice versa.
first of all let's see how our example will be looking like :


it's actually just a WebView which is displaying a local Html page, this html page contains a java script slideshow component, I've actually used the very same code that i found over here. the original javascript component has two button which can be used to slide into the next or previous page, but what I wanted was to be able to slide through pages using Swiping Gestures, Like...Swipe Left...go to next page, Swipe Right ...previous page.


There are two javaScript functions , moveBackward() which replaces the current Item with previous one using a left to right sliding effect and moveForward() which does the exact opposite thing. all we need to do is to detect user Gestures, if it is a right to left gesture (swipe left) we will call moveForward()function and if it is a swipe right gesture we will call moveBackward() function, in this example I've used SimpleGestureFilter class (see my last post) and here is our onSwipe method implementation:



@Override
public void onSwipe(int direction) {


switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");
break;
case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");
break;
case SimpleGestureFilter.SWIPE_DOWN :
case SimpleGestureFilter.SWIPE_UP :

}
}


It's much easier than you though it would be, isn't it? we just need to pass the name of our javascript function (with 'javascript:' prefix) to loadUrl() method of the WebView instance.
as you can see in the pictures above, we have 3 buttons in our html form 'Add', 'Remove' and 'Report', we also have a Vector of Strings in our Activity; when 'Add' button is pressed the name of the current item will be sent to our activity and got stored in the Vector, when 'Remove' button is pressed the name of current item will be removed from Vector and in both cases a message will be shown afterwords that the operation has just been done :


if user presses the "Report" button a javascript popup box will be shown which says how many items are currently stored in our Vector:


here is the html and javascript code that is being used to render those 3 buttons:


.
.
.
var items = ["British Columbia", "Ontario", "Yukon","New Brunswick"];

function report(){

var contentStr = window.interface.getReportContent();

var contentDiv = document.getElementById("showBoxContent");
contentDiv.innerHTML = contentStr;
showBox();
}
.
.
.
<input type="button" onclick="window.interface.add(items[configParams.selected])" value="Add"/>
<input type="button" onclick="window.interface.remove(items[configParams.selected])" value="Remove"/>
<input type="button" onclick="report()" value="Report"/>
.
.
.


and here is our Activity source code :



public class WebViewSample extends Activity implements SimpleGestureListener{

private Handler handler = new Handler();
private WebView webView;
private SimpleGestureFilter filter;
private Vector<String> provinces = new Vector();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.webview_layout);
webView = (WebView) findViewById(R.id.webview);

WebSettings webSettings = webView.getSettings();
webSettings.setSaveFormData(false);
webSettings.setJavaScriptEnabled(true);

this.filter = new SimpleGestureFilter(this,this);
this.filter.setMode(SimpleGestureFilter.MODE_TRANSPARENT);

webView.addJavascriptInterface(new MyJavaScriptInterface(), "interface");

webView.loadUrl("file:///android_asset/test.html");
}

@Override
public boolean dispatchTouchEvent(MotionEvent me){
this.filter.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}

@Override
public void onSwipe(int direction) {

switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");
break;
case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");
break;
case SimpleGestureFilter.SWIPE_DOWN :
case SimpleGestureFilter.SWIPE_UP :

}
}


@Override
public void onDoubleTap() {
}


private void addOrRemove(String name,boolean add){

String msg = null;
boolean alreadyAdded = this.provinces.contains(name);

if(add){
if(!alreadyAdded){
this.provinces.add(name);
msg = name+" Has just been added to the List.";
}
else
msg = name+" Has already been added to the list.";
}
else{
if(alreadyAdded){
this.provinces.remove(name);
msg = name+" Has just been removed from the List.";
}
else
msg = name+" has never been added to the list!";
}

Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}


final class MyJavaScriptInterface {

MyJavaScriptInterface() {
}

public void add(final String name) {
handler.post(new Runnable() {
public void run() {
addOrRemove(name, true);
}
});
}

public void remove(final String name) {
handler.post(new Runnable() {
public void run() {
addOrRemove(name, false);
}
});
}

public String getReportContent() {
if(provinces.size() == 0)
return "<i> The list is empty. </i>";

String content = "<i>You have added these provinces into the list :<i><br/><br/>";
for(int i=0;i<provinces.size();i++)
content += (i+1)+"- "+provinces.get(i)+"<br/>";

return content;
}

}

}


as you can see all we have is a simple WebView instance which displays the content of 'test.html' file that is located in assets directory of our project.

we've called addJavascriptInterface() method of our WebView instance in order to be able to call java methods in javascript code this method has two parameters, the first one is an Object methods of which we want to call in our javascript code and the second parameter is an String which will be used as the name of that java object in javascript context. once addJavascriptInterface() method is called the specified java object will be attached to the Html document's window object and can be referred by its name.
Remember that when methods of the passed java object is called in javascript context, it's not gonna invoked in the Main(UI) thread, and if you need to get it called in the UI thread you will need to use a Handler and post a new runnable to run your code, just like what I've done for add() and remove() methods in this example (though it is not necessary in this example).
another important thing to remember is the fact that you cannot pass or return any complex object to or from javascript context, that is all you are allowed to use are primitive and String data types.

Tuesday, January 19, 2010

Android Gestures

Like it or not touch screens are becoming part of both developers and users life, i mean dont think i would buy one of Those uncool phones without touchscreen unless i really have to, would you? but the important point is that what is the Touchscreen use if applications doesn't support touchscreen interaction, in other words who is really gonna pay for a ,say, Picture management application if it does not support some gestures for switching between pictures or zooming? [ Gesture Detection in Android ].
In Android there are three levels of touch screen event handling mechanism which can be used by developers. the most low level technique is to receive all touch events and take care of all things, you can attach an 'OnTouchListener' to a view and get notified whenever there is a touch event or you can override onTouchEvent() or dispatchTouchEvent() method of your activity or view, in all these cases you would be dealing with an instance of MotionEvent every single time and you would have to detect what user is doing all on your own which will suit requirments for developing games and stuff like that. But it is just too much hassle if you only need a few simple gestures for your application.
Next approach is to use GestureDetector class along with OnGestureListener and/or OnDoubleTapListener, in this technique whenever there is a new MotionEvent you have to pass it to Gesture Detector's onTouchEvent() method, it then will analyse this event and previous events and tell you what is happening on the screen by calling some of the callback methods.
here is a simple activity which uses GestureDetector :



public class SimpleActivity extends Activity implements OnGestureListener,
OnDoubleTapListener{

private GestureDetector detector;

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

detector = new GestureDetector(this,this);
}

@Override
public boolean onTouchEvent(MotionEvent me){
this.detector.onTouchEvent(me);
return super.onTouchEvent(me);
}

@Override
public boolean onDown(MotionEvent e) {
Log.d("---onDown----",e.toString());
return false;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
Log.d("---onFling---",e1.toString()+e2.toString());
return false;
}

@Override
public void onLongPress(MotionEvent e) {
Log.d("---onLongPress---",e.toString());
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
Log.d("---onScroll---",e1.toString()+e2.toString());
return false;
}

@Override
public void onShowPress(MotionEvent e) {
Log.d("---onShowPress---",e.toString());
}

@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d("---onSingleTapUp---",e.toString());
return false;
}

@Override
public boolean onDoubleTap(MotionEvent e) {
Log.d("---onDoubleTap---",e.toString());
return false;
}

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.d("---onDoubleTapEvent---",e.toString());
return false;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.d("---onSingleTapConfirmed---",e.toString());
return false;
}

}


if we want to understand all gesture types and how they work, first of all we need to know three basic MotionEvents which can combine with each other and create some gestures, these three Events are Action_Down , Action_Move and Action_Up , each time you touch the screen an Action_Down occurs and when you start moving it will create Action_Move event and finally when you take your finger off the screen an Action_Up Event will be created.
onDown() is called simply when there is an Action_Down event.
onShowPress() is called after onDown() and before any other callback method, I found out it sometimes might not get called for example when you tap on the screen so fast, but it's actually what this method all about, to make a distinction between a possible unintentional touch and an intentional one.
onSingleTapUp() is called when there is a Tap Gesture. Tap Gesture happens when an Action_Down event followed by an Action_Up event(like a Single-Click).
onDoubleTap() is called when there is two consecutive Tap gesture(like a Double-Click).
onSingleTapConfirmed() is so similar to onSingleTapUp() but it is only get called when the detected tap gesture is definitely a single Tap and not part of a double tap gesture.
Here is the sequence in which these callback methods are called when we tap on the screen:

onDown() – onShowPress() - onSingleTapUp() – onSingleTapConfirmed()


and here is when we do a double tap:


onDown() – onShowPress() - onSingleTapUp() – onDoubleTap() – onDoubleTapEvent()
onDown() – onShowPress() – onDoubleTapEvent()


onFling() is called when a Fling Gesture is detected. fling Gesture occurs when there is an Action_Down then some Action_Move events and finally an Action_Up, but they must take place with a specified movement and velocity pattern to be considered as a fling gesture. for example if you put your finger on the screen and start moving it slowly and then remove your finger gently it won’t be count as a fling gesture.

onScroll() is usually called when there is a Action_Move event so if you put your finger on the screen and move it for a few seconds there will be a method call chain like this :


onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... - onScroll()


or If the movement was a Fling Gesture, then there would be a call chain like this :


onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... – onFling()

If there is an Action_Move event between first tap and second tap of a doubleTap gesture it will be handled by calling onDoubleTapEvent() instead of onScroll() method. onDoubleTapEvent() receives all Action_Up events of a doubleTap gesture as well.
Remember that if we touch the screen and don’t remove our finger for a specified amount of time onLongPress() method is called and in most cases there will be no further event callback regardless of whatever we do after that, moving or removing our finger. we can easily change this behavior of detector by calling setIsLongpressEnabled() method of GestureDetector class.
although GestureDetector makes our life much easier, it still could be a real pain in the ass if we would need to handle some complicated gestures, imagine you need an application which should do task1 when there is a circle gesture, task2 for rectangle gesture and task3 for triangle gesture. obviously it would not be so pleasant to deal with such a situation with those mechanism we have seen so far, it's actually when Gesture API and Gesture Builder Application come into play.
Gesture Builder Application comes with Android emulator as a pre-installed app, It helps us to simply make new gestures and save them on the device, then we can retrieve and detect those gestures later using Gesture API. I'm not gonna talk about Gesture Builder app here,since there is a pretty good Article about it on Android Developers blog.
the only thing I'd like to mention here is GestureOverlayView class, It is actually just a transparent FrameLayout which can detect gestures but the thing is it can be used in two completely different ways, you can either put other views inside it and use it as a parent view or put it is the last child view of a FrameLayout (or any other way which causes it to be placed on top of another view).
In the first Scenario all child views will receive Touch events, therefore we will be able to press buttons or interact with other widgets as well as doing some gestures, on the other hand if GestureOverlayView has been placed on top, it will swallow all Touch events and no underlay view will be notified for any touch Event.
Although Gesture API brings many useful features for us, I personally prefer to use GestureDetector for some simple, basic gestures and honestly I feel like something is missing here, I mean , apart from games, I would say more than 70% of all gestures that might be needed in our applications are just a simple sliding in different directions or a double tap. and that's why I have decided to come up with something easy which can enable us to handle those 70% as simple as possible. how simple? you might be asking...
here is how our previous activity will look like if we use SimpleGestureFilter class :



public class SimpleActivity extends Activity implements SimpleGestureListener{

private SimpleGestureFilter detector;

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

detector = new SimpleGestureFilter(this,this);
}

@Override
public boolean dispatchTouchEvent(MotionEvent me){
this.detector.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}

@Override
public void onSwipe(int direction) {
String str = "";

switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : str = "Swipe Right";
break;
case SimpleGestureFilter.SWIPE_LEFT : str = "Swipe Left";
break;
case SimpleGestureFilter.SWIPE_DOWN : str = "Swipe Down";
break;
case SimpleGestureFilter.SWIPE_UP : str = "Swipe Up";
break;

}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}

@Override
public void onDoubleTap() {
Toast.makeText(this, "Double Tap", Toast.LENGTH_SHORT).show();
}

}


and here is SimpleGestureFilter source code :



public class SimpleGestureFilter extends SimpleOnGestureListener{

public final static int SWIPE_UP = 1;
public final static int SWIPE_DOWN = 2;
public final static int SWIPE_LEFT = 3;
public final static int SWIPE_RIGHT = 4;

public final static int MODE_TRANSPARENT = 0;
public final static int MODE_SOLID = 1;
public final static int MODE_DYNAMIC = 2;

private final static int ACTION_FAKE = -13; //just an unlikely number
private int swipe_Min_Distance = 100;
private int swipe_Max_Distance = 350;
private int swipe_Min_Velocity = 100;

private int mode = MODE_DYNAMIC;
private boolean running = true;
private boolean tapIndicator = false;

private Activity context;
private GestureDetector detector;
private SimpleGestureListener listener;


public SimpleGestureFilter(Activity context,SimpleGestureListener sgl) {

this.context = context;
this.detector = new GestureDetector(context, this);
this.listener = sgl;
}

public void onTouchEvent(MotionEvent event){

if(!this.running)
return;

boolean result = this.detector.onTouchEvent(event);

if(this.mode == MODE_SOLID)
event.setAction(MotionEvent.ACTION_CANCEL);
else if (this.mode == MODE_DYNAMIC) {

if(event.getAction() == ACTION_FAKE)
event.setAction(MotionEvent.ACTION_UP);
else if (result)
event.setAction(MotionEvent.ACTION_CANCEL);
else if(this.tapIndicator){
event.setAction(MotionEvent.ACTION_DOWN);
this.tapIndicator = false;
}

}
//else just do nothing, it's Transparent
}

public void setMode(int m){
this.mode = m;
}

public int getMode(){
return this.mode;
}

public void setEnabled(boolean status){
this.running = status;
}

public void setSwipeMaxDistance(int distance){
this.swipe_Max_Distance = distance;
}

public void setSwipeMinDistance(int distance){
this.swipe_Min_Distance = distance;
}

public void setSwipeMinVelocity(int distance){
this.swipe_Min_Velocity = distance;
}

public int getSwipeMaxDistance(){
return this.swipe_Max_Distance;
}

public int getSwipeMinDistance(){
return this.swipe_Min_Distance;
}

public int getSwipeMinVelocity(){
return this.swipe_Min_Velocity;
}


@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {

final float xDistance = Math.abs(e1.getX() - e2.getX());
final float yDistance = Math.abs(e1.getY() - e2.getY());

if(xDistance > this.swipe_Max_Distance || yDistance > this.swipe_Max_Distance)
return false;

velocityX = Math.abs(velocityX);
velocityY = Math.abs(velocityY);
boolean result = false;

if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance){
if(e1.getX() > e2.getX()) // right to left
this.listener.onSwipe(SWIPE_LEFT);
else
this.listener.onSwipe(SWIPE_RIGHT);

result = true;
}
else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance){
if(e1.getY() > e2.getY()) // bottom to up
this.listener.onSwipe(SWIPE_UP);
else
this.listener.onSwipe(SWIPE_DOWN);

result = true;
}

return result;
}

@Override
public boolean onSingleTapUp(MotionEvent e) {
this.tapIndicator = true;
return false;
}

@Override
public boolean onDoubleTap(MotionEvent arg0) {
this.listener.onDoubleTap();;
return true;
}

@Override
public boolean onDoubleTapEvent(MotionEvent arg0) {
return true;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent arg0) {

if(this.mode == MODE_DYNAMIC){ // we owe an ACTION_UP, so we fake an
arg0.setAction(ACTION_FAKE); //action which will be converted to an ACTION_UP later.
this.context.dispatchTouchEvent(arg0);
}

return false;
}


static interface SimpleGestureListener{
void onSwipe(int direction);
void onDoubleTap();
}

}


as you can see clients of these class can determine the minimum and maximum distance and also minimum velocity which is required for a movement on screen to be considered as a Swipe Gesture, I also thought it would be great if our filter can behave differently like what GestureOverlayView can do and even more than that!
this Filter can run in three different mode: Transparent, Solid and Dynamic. in Transparent mode it will work just like when we have a GestureOverlayView as parent: all views will receive Touch events; Solid mode works like when we put a GestureOverlayView as a child view: no one will receive TouchEvent, it is not as efficient as GestureOverlayView is, since we actually let all events get passed but what we do is we literally kill them when they are passing through our filter ;).
the last mode is Dynamic mode, the primary purpose of this mode is to have a bit smarter gesture detection, i mean there has been sometimes that i wanted to slide from one page to another, but a button get pressed and something else happens. it does not happen so much but it is really annoying. what i tried to do in Dynamic mode is to distinguish between a swipe/double tap gesture and a movement which is neither of them. so if you have a view full of buttons and other interactive stuff and user does a swipe or double tap gesture, it is guaranteed (although i believe there is no such thing as guarantee in life ;) ) that no other event callback will be called but only onSwipe() or onDoubleTap().
Anyway that's what i came up with to take the pain away in those circumstances when we just need to handle some simple Gestures.
hope it will be helpful for you and can make your life a bit easier.

Saturday, January 16, 2010

Android Braodcast Receivers

As we all know There are four types of components which can be used in Android, a good android developer should be Familiar with these components and the idea behind them in order to design and implement efficient applications.
I think it is safe to say that the most significant thing about android is the sharing philosophy on which it is based and understanding this concept could help us to have a more solid view on when and how to use each one of these component types.
Android applications can share their stuff and interact with each other and that is the main idea behind Content Providers,Services and Broadcast Receivers in Android: Sharing Data, Services and Events.
In this article I am gonna talk about Broadcast Receivers in Android and I will try to cover almost everything that we might need to know about them in different situations. Broadcast Receiver is actually a mechanism to send and receive events so that all interested applications can be informed when something happens. there are heaps of System events which get broadcast by Android OS such as SMS related events, Connectivity related events, Camera related events and many more.
We are able to broadcast our application specific events as well, so for example if we have a RSS news reader application and we want to do something whenever a new item is available, it would be a good idea to use Broadcast Receiver method, not only because it will separate your event handling code but most importantly because it will enable other applications to register and receive a notification whenever that event takes place.
The first thing we will be talking about is how to implement a Receiver and register it so that you can receive events that you want to receive. Implementing a Receiver is pretty simple and straight forward, all you need to do is to extend Broadcast Receiver class and override its onReceive() method. once you implement your Receiver you need to register it and determine what kind of events you are interested in to be sent to you.
For example if you want to get notified whenever there is an incoming call or SMS you will need to add something like this to you manifest file :



<receiver android:name="amir.android.icebreaking.CallAndSMSListener">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>


and here is CallAndSMSListener class source code :



public class CallAndSMSListener extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

String action = intent.getAction();
if(action.equalsIgnoreCase("android.intent.action.PHONE_STATE")){
if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
TelephonyManager.EXTRA_STATE_RINGING)) {

doSomething(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
}
}
else {

Bundle bundle = intent.getExtras();
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage message = SmsMessage.createFromPdu((byte[])pdus[0]);
if(!message.isEmail())
doSomething(message.getOriginatingAddress());

}

}

private void doSomething(String number){
Log.d("<<<>>>",number);
}

}


In this example I have registered my Receiver in Manifest file, but we can also register a Receiver dynamically in our code like this :



this.reciever = new ConnectivityListener();
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(this.reciever, filter);


in this case after calling registerReceiver our ConnectivityListener will be notified when Wi-Fi state or Connectivity state of mobile phone change. Just remember to unregister all Receivers that you have registered in your code using unregisterReceiver() method.
ConnectivityListener class looks like this :



public class ConnectivityListener extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

if(action.equalsIgnoreCase(WifiManager.NETWORK_STATE_CHANGED_ACTION)){

int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
//Do Something.....
}
else {

NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
//Do Something.....

}


}

}


So far we have seen how to register a Receiver both statically and dynamically,but it is just half the story; as i said we can not only receive events but can also broadcast our events either locally for just a specific Receiver or globally for all other applications.
In my application I have something like this :



Intent intent = new Intent(this,MyReceiver.class);
intent.putExtra("Message", "Something has Changed!!");

this.sendBroadcast(intent);


As you can see i specified the class type for the Intent, so when i broadcast the Intent, only MyReceiver will be notified. if we wanted to globally broadcast it we should set an Action string for our Intent so that all receivers which have indicated that action in their intent filter would be notified.
this is what you need to put in your manifest file for this example:



<receiver android:name=".MyReceiver"/>



and MyReceiver class:



public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

String msg = intent.getStringExtra("Message");
Vibrator vib = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);

Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
vib.vibrate(2000);

}

}


Sometimes you dont want to broadcast an event directly from your activity but for whatever reason you want another application do that on your behalf, in these sort of cases you should use PendingIntent class instead of Intent. AlarmManager class is a good example to illustrate this kind of scenarios, AlarmManager class allows you to set a time to alarm system goes off, you can also specify a PendingIntent object to be broadcast when alarm goes off.
I have used this piece of code in my Activity onStop() method :



Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.HOUR, 4);

Intent intent = new Intent(this, Receiver.class);
intent.setAction("Start");
PendingIntent sender = PendingIntent.getBroadcast(this,
0, intent, 0);

AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);


It means that alarm goes off 4 hours after each time we close the application and when it happens it also broadcast the specified intent it has been provided earlier.
and here is the content of Reciever's onRecieve() method :



Intent startIntent = new Intent(context, MainActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

context.startActivity(startIntent);


so simple, it just starts our activity again. 4 hours after that we close the application the alarm goes off and the application comes up again.

when we send an ordinary broadcast all Registered Receivers will get notified but the order in which they are notified is undefined, in other words you wouldn't know which Receiver is called first and which one last. another thing to remember is you are not allowed to stop the propagation of an event in a Receiver. but what if you really need to be able to stop the propagation of an event in an Receiver so that other receivers dont get it or what if you need to set an order according to which Receivers should be called, thankfully it's not a big deal, all we would need to do is to call sendOrderedBroadcast() method instead of sendBroadcast() to solve the problem.
For example I have used this code in my application :



Intent intent = new Intent("com.test.ACTION");
intent.putExtra("Random", (int)(Math.random()*10));

this.sendOrderedBroadcast(intent,
null,
new LastReceiver(),
null,
Activity.RESULT_OK,
null,
null);


I also have two receivers in my manifest file :



<receiver android:name="amir.android.icebreaking.FirstReceiver">
<intent-filter android:priority="100">
<action android:name="com.test.ACTION" />
</intent-filter>
</receiver>

<receiver android:name="amir.android.icebreaking.SecondReceiver">
<intent-filter android:priority="50">
<action android:name="com.test.ACTION" />
</intent-filter>
</receiver>


When we call OrderedBroadcast() method we have two registered Receiver which need to be called, but as you can see we have used android:priority attribute for them, it means that Receiver with higher value will be called first, in this example FirstReciever is notified first and then SecondReciever get notified if FirstReciever have not aborted the intent.
LastReciever is always gonna get notified regardless of what has happened in other Receivers.
and here is these three classes source codes :



public class FirstReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

int value = intent.getIntExtra("Random", -1);

Bundle bundle = getResultExtras(true);
bundle.putInt("FirstReciever", value*13);
setResultExtras(bundle);

if(value < 0 || value%2 != 0)
abortBroadcast();

}

}




public class SecondReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

int value = intent.getIntExtra("Random", -1);
if(value > 0){
Bundle bundle = getResultExtras(true);
bundle.putInt("SecondReciever", value*17);
setResultExtras(bundle);
}


}

}





public class LastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {

Bundle bundle = getResultExtras(false);
StringBuilder builder = new StringBuilder();
builder.append("---Last Reciever---\n");
builder.append("<<>>"+intent.getIntExtra("Random", -1)+"\n");
builder.append("<<>>"+bundle.getInt("FirstReciever")+"\n");
builder.append("<<>>"+bundle.getInt("SecondReciever")+"\n");

Toast.makeText(context, builder.toString(), Toast.LENGTH_LONG).show();

}

}



Do not forget to add these Permission if you want to test these examples:



<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />

Wednesday, January 6, 2010

Android Preferences

For almost any application we need to provide some settings in order to enable users have some level of control over how the application works. Android has provided a standard mechanism to show, save and manipulate user's preferences. PreferenceActivity class is the standard Android Activity to show Preferences page, it contains a bunch of Preference class instances which use SharedPreference
class to save and manipulate corresponding data.There are different types of Preferences Available in Preference package, and if you need something more you can extend Preference class and create your own Preference type. in this article we will go through predefined Preference types in Android and I'm also going to implement a custom Preference type to see how that works.
Our Preferences page is gonna look like this :



as you can see we have two section in our preferences page , "First Category" which contains two options and "Second Category" which contains three options.
here is our Activity which produces this page :



public class MyPreferenceActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.preferences);
}

}


pretty simple, isn't it? actually it's supposed to be simple and easy thanks to PreferenceActivity which takes care of pretty much anything. as i said PreferenceActivity renders instances of Preference class which in this example
have defined in preferences.xml file under res/xml directory:



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

<PreferenceCategory android:title="First Category">
<CheckBoxPreference
android:key="Main_Option"
android:title="Main Option"
android:defaultValue="true"
android:summary="SUMMARY_Main_Option" />


<ListPreference
android:title="List Preference"
android:summary="This preference allows to select an item in an array"
android:dependency="Main_Option"
android:key="listPref"
android:defaultValue="1"
android:entries="@array/colors"
android:entryValues="@array/colors_values" />


</PreferenceCategory>

<PreferenceCategory android:title="Second Category">

<PreferenceScreen android:title="Advanced Options">

<CheckBoxPreference
android:key="Advanced_Option"
android:title="Advanced Option"
android:defaultValue="true"
android:summary="SUMMARY_Advanced_Option"/>

</PreferenceScreen>


<amir.android.icebreaking.SeekBarPreference
android:dependency="Main_Option"
android:key="customPref"
android:defaultValue="32"
android:title="Custom Preference" />

<EditTextPreference android:dialogTitle="EditTextTitle"
android:dialogMessage="EditTextMessage"
android:dependency="Main_Option"
android:key="pref_dialog"
android:title="SomeTitle"
android:summary="Summary"
android:defaultValue="test"/>



</PreferenceCategory>

</PreferenceScreen>



First thing i wanna talk about is dependency, Dependency helps you to disable/enable some Preferences based on the status of another preference. in this example we have three preferences dependent on the first preference so if we disable the first option all dependent options will become uneditable.



if we select the second option we will see something like this :



ListPreference tag has two attribute which is used to populate its content; android:entries which refers to an array of labels and android:entryValues which is also an array that represent the actual value of entries, this value will be saved when each entry gets selected. as you can see I've used "@array/" indicator for these two attribute, it means we have our data in arrays.xml file under res/values directory and here is its content :



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


<resources>

<string-array name="colors">
<item>red</item>
<item>orange</item>
<item>yellow</item>
<item>green</item>
<item>blue</item>
<item>violet</item>
</string-array>

<string-array name="colors_values">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</string-array>


</resources>



you sometimes want to show a set of Preferences in a different window, to do so you just need to wrap all those Preferences in a PreferenceScreen tag, just like what I've done for "Advanced Options" in this example so when user presses this option we will see something like this :



The last Predefined Preference type I'm gonna talk about is EditTextPreference, when user presses one of these type of Preferences, a dialog with a Text Field pops up and let the user enter a text. I used a EditTextPreference for the last option in this example and you can see here how it's gonna look like after being pressed :



So far we've seen what functionality we have already got in our hand which would fulfill our needs in most circumstances but what if we need something that is not already there? Not a big deal... we'll just need to roll up our sleeves and implement our own Preference type.
I've developed a simple custom preference type which shows a seekbar and stores an Integer value each time user moves the seekbar. you can see how it looks like in the first picture above.
here is our SeekBarPreference class source code :



public class SeekBarPreference extends Preference
implements OnSeekBarChangeListener{


public static int maximum = 100;
public static int interval = 5;

private float oldValue = 50;
private TextView monitorBox;


public SeekBarPreference(Context context) {
super(context);
}

public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}

public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
protected View onCreateView(ViewGroup parent){

LinearLayout layout = new LinearLayout(getContext());

LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params1.gravity = Gravity.LEFT;
params1.weight = 1.0f;


LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(
80,
LinearLayout.LayoutParams.WRAP_CONTENT);
params2.gravity = Gravity.RIGHT;


LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(
30,
LinearLayout.LayoutParams.WRAP_CONTENT);
params3.gravity = Gravity.CENTER;


layout.setPadding(15, 5, 10, 5);
layout.setOrientation(LinearLayout.HORIZONTAL);

TextView view = new TextView(getContext());
view.setText(getTitle());
view.setTextSize(18);
view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
view.setGravity(Gravity.LEFT);
view.setLayoutParams(params1);


SeekBar bar = new SeekBar(getContext());
bar.setMax(maximum);
bar.setProgress((int)this.oldValue);
bar.setLayoutParams(params2);
bar.setOnSeekBarChangeListener(this);

this.monitorBox = new TextView(getContext());
this.monitorBox.setTextSize(12);
this.monitorBox.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC);
this.monitorBox.setLayoutParams(params3);
this.monitorBox.setPadding(2, 5, 0, 0);
this.monitorBox.setText(bar.getProgress()+"");


layout.addView(view);
layout.addView(bar);
layout.addView(this.monitorBox);
layout.setId(android.R.id.widget_frame);


return layout;
}

@Override
public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {

progress = Math.round(((float)progress)/interval)*interval;

if(!callChangeListener(progress)){
seekBar.setProgress((int)this.oldValue);
return;
}

seekBar.setProgress(progress);
this.oldValue = progress;
this.monitorBox.setText(progress+"");
updatePreference(progress);

notifyChanged();
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}


@Override
protected Object onGetDefaultValue(TypedArray ta,int index){

int dValue = (int)ta.getInt(index,50);

return validateValue(dValue);
}


@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

int temp = restoreValue ? getPersistedInt(50) : (Integer)defaultValue;

if(!restoreValue)
persistInt(temp);

this.oldValue = temp;
}


private int validateValue(int value){

if(value > maximum)
value = maximum;
else if(value < 0)
value = 0;
else if(value % interval != 0)
value = Math.round(((float)value)/interval)*interval;


return value;
}


private void updatePreference(int newValue){

SharedPreferences.Editor editor = getEditor();
editor.putInt(getKey(), newValue);
editor.commit();
}

}


In all examples I've come across about custom preferences, an XML layout file has been used to create the preference view, so I thought it would be a good idea not to used XML and go programatically and actually it was a good opportunity for a person like me who had always used XML for creating GUIs in Android to go through an alternative way.

anyway onCreateView() method of Preference class is responsible to return a View to be shown by PereferenceActivity,we override this method to make our own custom view, in Preference class documentation we've been asked to return a ViewGroup with "widget_frame" as its ID, that's what I've done and you should do if you want to do something like this.
Another thing to remember is that if you're willing to let clients of this preference type set Listeners and get notified when the status of preference changes you have to call callChangeListener() method before saving new value, this method will invoke the listener callback method and return the result, if client is happy with new value and operation can be carried out the result will be true, otherwise it will be false. Don't forget to call notifyChanged() method once you have saved new value of preference, though I'd forgotten to do so and it was working like a charm ;)

I've also used both Preference class's persistInt() method and Editor class's putInt() method to store data in this example to show different ways that it can be done.

And here is a simple Activity which retrieves preferences values and show them in a textview :



public class ShowSettings extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show);


SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);

StringBuilder builder = new StringBuilder();

builder.append("\n"+ sp.getBoolean("Main_Option",false));
builder.append("\n"+ sp.getString("listPref","-1"));
builder.append("\n"+ sp.getBoolean("Advanced_Option",false));
builder.append("\n"+ sp.getInt("customPref",-1));
builder.append("\n"+ sp.getString("pref_dialog","NULL"));

TextView view = (TextView)findViewById(R.id.viewBox);
view.setText(builder.toString());

}

}