Thursday, October 21, 2010

Message handlers

I was facing a runtime exception like below. I couldnt figure out what was the mistake am doing. Then i went and did some google results and found out how to avoid this and why this exception happened.



E/AndroidRuntime( 60): *** FATAL EXCEPTION IN SYSTEM PROCESS: Timer-0
E/AndroidRuntime( 60): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime( 60): at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChild(ViewRoot.java:607)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:633)
E/AndroidRuntime( 60): at android.view.ViewGroup.invalidateChild(ViewGroup.java:2505)
E/AndroidRuntime( 60): at android.view.View.invalidate(View.java:5139)
E/AndroidRuntime( 60): at android.widget.TextView.checkForRelayout(TextView.java:5364)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2688)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2556)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2531)
E/AndroidRuntime( 60): at com.android.server.status.StatusBarService$1$1.run(StatusBarService.java:403)
E/AndroidRuntime( 60): at java.util.Timer$TimerImpl.run(Timer.java:289)


Reason :

Every activity/application when started it creates a main UI thread to show the UI elements. When a new thread is created from main UI thread, and when UI elements where tried to access from this thread, then this exception will be thrown,
Only the original thread that created a view hierarchy can touch its views

So how do we acheive this,??

This scenario is common, we create a thread in main thread to do some light weight work, like fetching some data from net, to wait for some event from other process, and when the event is successful, we would like to update the UI elements.

Message handlers are used to achieve this.

http://developer.android.com/reference/android/os/Handler.html



"When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will than be scheduled in the Handler's message queue and processed when appropriate."



The above explanation makes it clear.

Each handler instance when created will be linked to the thread where it is created.Each thread has its own messageQueue and using this handler we can deliver the messages, runnables to this thread.

So by keeping the message handler object global, it can use used in other thread apart from main UI thread to deliver messages to main UI thread.

The below picture portrays what it is in high level picture.




I have written a simple example to explain the message handler. The application has one textview and one button.

On Click of the button, it creates a new thread() and fetches data from google local search about "pubs london" and when the response is received, this thread uses message handler to deliver the message event to main thread.(UI thread ).

Lets see in detail in code:

Layout.xml
--------------



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fetch Data" />
</LinearLayout>



handler.java
-------------



package com.android.handler;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class handler extends Activity {
private String responseText;
private TextView content;
private ProgressDialog MyDialog;
private Context mContext;
private final int UPDATE_TEXT = 0;
private Handler messageHandler = new Handler() {

public void handleMessage(Message msg) {
super.handleMessage(msg);

if(msg.what == UPDATE_TEXT)
{
MyDialog.dismiss();
content.setText(responseText);
}
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = getApplicationContext();
content = (TextView)findViewById(R.id.text);
MyDialog = new ProgressDialog(this);
MyDialog.setMessage("Fetching...");
MyDialog.setIndeterminate(true);
MyDialog.setCancelable(true);

Button b = (Button)findViewById(R.id.button1);
b.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v)
{

MyDialog.show();
new Thread() {
public void run() {
try {
sendSearchRequest();
} catch (Exception e) {
}

}
}.start();

}

});
}
public void sendSearchRequest()
{
HttpClient client = new DefaultHttpClient();
String query = "http://ajax.googleapis.com/ajax/services/search/local?hl=en&v=1.0&rsz=8&q=pubs london&start=0";
try {
URL url = new URL(query);
URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(), url.getQuery(), null);
HttpGet request = new HttpGet(uri);
HttpResponse response = client.execute(request);
Userrequest(response);
}catch (URISyntaxException e){

}
catch(Exception ex){
content.setText("Failed expcetion");
}

}
public void Userrequest(HttpResponse response){

try{
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder str = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null){
str.append(line + "\n");
}
in.close();
responseText = str.toString();
messageHandler.sendEmptyMessage(UPDATE_TEXT);

}catch(Exception ex){
responseText = "Error";
}

}
}

Frame animation - How to know when animation ends?

In related to my earlier post how to use frame animation and implement a circular spinner, i noted a important factor of AnimationDrawable.

I wanted to do some work once the frame animation ends.I looked for animation listener for AnimationDrawable, unfortunately animationDrawable class doesnot have facility to listen for animationlistener object.

SO when googled i found out some suggestions and help in forums. So i am gonna share what i tried out.

1 - We need to calculate the total duration of each frame shown, using getNumberOfFrames() and getDuration() apis.

2 - As soon as the animation is started, Start a timer which will trigger timertask after the total duration. You can conclude now that animation would have ideally stopped. Then you can use stop api to stop the animation.



animation = new AnimationDrawable();

animation.addFrame(getResources().getDrawable(R.drawable.gradient_25), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_50), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_75), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_100), 500);
animation.setOneShot(true);

// Code continues................
// Code continues.................

Button start = (Button ) findViewById(R.id.start);
start.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.start();

long totalDuration = 0;
for(int i = 0; i< animation.getNumberOfFrames();i++){
totalDuration += animation.getDuration(i);
}
Timer timer = new Timer();

TimerTask timerTask = new TimerTask(){
@Override
public void run() {

animation.stop();
}
}};
timer.schedule(timerTask, totalDuration);
}

});



Note: Once you call start on animation object, you need to call 'stop()' on the animation object to see the animation again when start() called again. otherwise, animation wont start.

Tuesday, October 19, 2010

Frame animation - circular spinner implementation

With respect to earlier post of how to implement progress bar using level-list drawable, today i will take you in steps of achieving a similar kinda of animation using Frame animation.

Level-list & animation-list both are used for changing the images.

<Level-list> has some meaning like based on some conditions, or level u can pick the up the image. Where as resource just shows the frame one by one with a duration applied to each frame.

1 - Take the images and keep in drawable directory. And write the animation xml file and place it in anim folder. (/res/anim )



<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/circular1" android:duration="500" />
<item android:drawable="@drawable/circular2" android:duration="500" />
<item android:drawable="@drawable/circular3" android:duration="500" />
<item android:drawable="@drawable/circular4" android:duration="500" />
<item android:drawable="@drawable/circular5" android:duration="500" />
<item android:drawable="@drawable/circular7" android:duration="500" />
<item android:drawable="@drawable/circular8" android:duration="500" />
<item android:drawable="@drawable/circular9" android:duration="500" />
<item android:drawable="@drawable/circular10" android:duration="500" />
<item android:drawable="@drawable/circular11" android:duration="500" />
<item android:drawable="@drawable/circular1" android:duration="500" />
</animation-list>



I have kept the circular spinner images in drawable directory.

android:duration - Tells in milliseconds a frame would be shown.
android:oneshot - If true, keep showing frames in a loop from starting to ending and then again starting. If false, when animation started, shows the frames only once from starting to ending.

2 - This animation can be applied to all widgets (to a linear layout, Imageview, Button etc. )

There are two approaches you can do this.

1 - Create the animation-list drawable and set it in the layout's background property / or set it in the code. ( 2 ways )



<LinearLayout android:id="@+id/urlBar"
android:layout_width="fill_parent"
android:layout_height="50px"
android:background="@anim/layoutanimation"
android:orientation="horizontal">

or

ViewGroup lyout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundResource(R.anim.layoutanimation);




Next step is common for noth of the above cases to get the AnimationDrawable object.



AnimationDrawable animation;
animation = (AnimationDrawable) l.getBackground();
animtion.start();



2 - Another way is create a AnimationDrawable object and add frames to it. Then set the drawable object as backgroundDrawable to the widget.



AnimationDrawable animation;
animation.addFrame(getResources().getDrawable(R.drawable.circular1), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular2), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular3), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular4), 500);
animation.setOneShot(false);

ViewGroup layout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundDrawable(animation);

animation.start();



One catch here, which every one must know is, if you expect your animation to start in the onCreate() of activity, then sorry, it will not start.

http://developer.android.com/guide/topics/graphics/2d-graphics.html

Please read at the end of the page. It says why it will not work. :)

Now lets look at full example.
1 - Apply frame animation to Linear Layout. [a set of gradient images will be applied]
2 - Apply frame animation to a Image View. [ circular spinner holder ]




layoutanimation.xml



<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/gradient_25" android:duration="500" />
<item android:drawable="@drawable/gradient_50" android:duration="500" />
<item android:drawable="@drawable/gradient_75" android:duration="500" />
<item android:drawable="@drawable/gradient_100" android:duration="500" />

</animation-list>



imageanimation.xml


<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/circular1" android:duration="500" />
<item android:drawable="@drawable/circular2" android:duration="500" />
<item android:drawable="@drawable/circular3" android:duration="500" />
<item android:drawable="@drawable/circular4" android:duration="500" />
<item android:drawable="@drawable/circular5" android:duration="500" />
<item android:drawable="@drawable/circular7" android:duration="500" />
<item android:drawable="@drawable/circular8" android:duration="500" />
<item android:drawable="@drawable/circular9" android:duration="500" />
<item android:drawable="@drawable/circular10" android:duration="500" />
<item android:drawable="@drawable/circular11" android:duration="500" />
<item android:drawable="@drawable/circular1" android:duration="500" />
</animation-list>



layout main.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout android:id="@+id/urlBar"
android:layout_width="fill_parent"
android:layout_height="50px"
android:background="@anim/layoutanimation"
android:orientation="horizontal">

<Button android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="50px"
android:text="start" />

<Button android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="50px"
android:text="stop"/>
</LinearLayout>

<ImageView android:id="@+id/img"
android:layout_width="50px"
android:layout_height="50px"/>

</LinearLayout>



frameanimation.java



import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class frameanimation extends Activity {
AnimationDrawable animation;
AnimationDrawable animation1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
animation = new AnimationDrawable();

animation.addFrame(getResources().getDrawable(R.drawable.gradient_25), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_50), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_75), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_100), 500);
animation.setOneShot(false);


ViewGroup layout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundDrawable(animation);

ImageView imageAnim = (ImageView) findViewById(R.id.img);
imageAnim.setBackgroundResource(R.anim.imageanimation);
animation1 = (AnimationDrawable) imageAnim.getBackground();

Button start = (Button ) findViewById(R.id.start);
start.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.start();
animation1.start();
}

});
Button stop = (Button ) findViewById(R.id.stop);
stop.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.stop();
animation1.stop();
}

});
}

}


Custom view class and fonts handling for drawing text.

This post tells about how to write a custom view, and how to deal with fonts heights,and things needs to taken care while implementing custom view.



I was trying to implement a layout which looked exactly like above.

It was a horizontal linear layout, where the fonts were cropped in top & bottom by a bit of pixels.

I was going in the following approaches.

1 - A horizontal linear layout which had two textviews. And set the textSize attribute to '24px', which was the height of the parent horizontal layout.Expected the string will start from the top corner of the parent layout (0,0). But it dint happen.



<LinearLayout
android:layout_width="fill_parent"
android:layout_height="24px"
android:orientation="horizontal"
android:background="#ffffff">

<TextView android:id="@+id/urltextbox1"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="90"/>

<TextView android:id="@+id/urltextbox2"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="12:24p"/>

</LinearLayout>



And the output was like below.




Now the problem is , the fonts doesnt start exactly at the top - corner ( 0, 0) of the parent ViewGroup. TextView leaves some space and then started the text.

Later i tried to set diff parameters/attributes of textView thinking it will push the fonts above the size.
- android:height = "28px" --> Dint work. Still it left some padding in the top.

- android:includeFontPadding="false" --> Some hope. It reduced some space in the top.Removed the font padding at the top. ( Mainly this is left by TextView for languages like german where characters will have something like this. )



Then finally decided to write a custom view class and draw the text using canvas.

Here two things are important, fontSize/textSize and drawText- Y co-ordinate, Which tells canvas from where the text to start for drawing.


- Create paint object. Set textsize, padding, textcolor etc.
- Override onMeasure(), OnDraw() functions.
- onMeasure() - set the required width and height.


Some calculations are required to acheieve the fonts being cropped at the top & bottom.

Layout height = 24px.

We need to calculate 'y' value of drawText() api, which determines the text drawing starting point.



canvas.drawText(content,leftPadding, y, mPaint);

If we start at 0, then fonts wont be seen only.
If we start at 24, then fonts wont grow out below the layout's width. But it can grow on if we keep the textSize more than 24.

So it has to be 3 or 4 px > 24, may be 28px. So that font drawing will start from 28px and 4px height will be cropped in the bottom. And keeping proper textSize, it will grow beyond height of 24px and it will be cropped in top as well.




- setScaling() takes a float value, which multiplied by layout heights gives the starting point of font in drawText api.

- setText() will take the text.



 

package com.android.server.status;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.graphics.Typeface;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Rect;

public class FontFitTextView extends View {

private String content="";
private Paint mPaint;
private float scaling;
private float textSize=0.0f;
private int leftPadding= 0;

public FontFitTextView(Context context) {
super(context);
init();
}

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

private void init(){

mPaint = new Paint();
// set's the paint's colour
mPaint.setColor(Color.rgb(0x58, 0x58, 0x58));
// smooth's out the edges of what is being drawn
mPaint.setAntiAlias(true);

mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
}

public void setScaling(float scale)
{
this.scaling = scale;
}

public void setText(String text)
{
this.content = text;
invalidate();
}
public void setTextSize(float size)
{
this.textSize = size;
}
public void setPadding(int left,int right,int top,int bottom)
{
this.leftPadding = left;
}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{

int height = MeasureSpec.getSize(heightMeasureSpec);

if(textSize == 0.0f )
{
textSize = height + ((height* 50 ) / 100);
mPaint.setTextSize(textSize);
}
else
mPaint.setTextSize(textSize);

int width =(int) mPaint.measureText(content) + leftPadding;
setMeasuredDimension(width,height);

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

float y = getHeight() * scaling;

if(textSize == 0.0f )
{
textSize = getHeight() + ((getHeight()* 50 ) / 100);
mPaint.setTextSize(textSize);
}
else
mPaint.setTextSize(textSize);

System.out.println(" y " + " " + y + " text size " + textSize+"padding "+ leftPadding);

canvas.drawText(content,leftPadding, y, mPaint);

}

}

Tuesday, October 12, 2010

Progress bar implementation using Level list drawable.

In this post i am gonna cover how to implement battery level changes by changing the images using Level list drawable resource.

First create a set of images you want to show for sequence of progress bar.
In this example, these were the images taken to show percentages of battert level from 10 to 100.


level-list is a XML definition of a drawable resource which manages alternate drawables for different max & minimum levels. This can be applied to a View, or widget ,ImageView,Button etc. Once this is applied, the levels can be changed using api,
setLevel() and setImageLevel()

1 - Consider the below xml file [ images.xml ]. It defines the maxLevel values and its corresponding drawable ( in this example, it picks up the different battery images] .


images.xml:
-----------


<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="0" android:drawable="@drawable/batt_10" />
<item android:maxLevel="1" android:drawable="@drawable/batt_20" />
<item android:maxLevel="2" android:drawable="@drawable/batt_30" />
<item android:maxLevel="3" android:drawable="@drawable/batt_40" />
<item android:maxLevel="4" android:drawable="@drawable/batt_50" />
<item android:maxLevel="5" android:drawable="@drawable/batt_60" />
<item android:maxLevel="6" android:drawable="@drawable/batt_70" />
<item android:maxLevel="7" android:drawable="@drawable/batt_80" />
<item android:maxLevel="8" android:drawable="@drawable/batt_90" />
<item android:maxLevel="9" android:drawable="@drawable/batt_100" />
</level-list>




2 - Create a imageView in main.xml which forms the activities content. And note here, set the images.xml as a src to imageView property.

android:src="@drawable/images"

Layout:

main.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="battery-progress"
/>
<ImageView
android:id="@+id/battery"
android:layout_width="fill_parent"
android:layout_height="40px"
android:src="@drawable/images"
/>
</LinearLayout>



3 - Inside the activity we are gonna access the imageView and set a setImagelevel() api with different values from 0 to 10 and repeat this for indefinite number of times.




package com.android.imageselector;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;

public class imageselector extends Activity {
/** Called when the activity is first created. */
int i=0;
ImageView v;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

v = (ImageView)findViewById(R.id.battery);
int delay = 1000; // delay for 1 sec.
int period = 1000; // repeat every 10 sec.
Timer timer = new Timer();

final Handler messageHandler = new Handler() {

public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 0)
{
if(i<10)
{
i++;
v.setImageLevel(i);
}
else
{
i=0;
v.setImageLevel(i);
}
}

}
};

timer.scheduleAtFixedRate(new TimerTask()
{
public void run()
{

messageHandler.sendEmptyMessage(0);

}
}, delay, period);

}
}



4 - A timer object is created and scheduleAtFixedRate() api is used to invoke the thread for every 1 sec and call Handler object to set the imagelevel value from 0 to 10.

5 - Now the total outcome looks like, the battery images are set for values from 0 to 10 from the images.xml file. And looks like a battery is charged.Similarly the images can be changed as per your requirement to show the circular progress bar images, or horizontal progress bar images based on the progress levels.

Saturday, October 9, 2010

How to identify when lock screen is unlocked.?

My requirement was to listen for the intent when the screen is unlocked successfully.
Unfortunately, there is no such intent defined by Android framework, when the screen is unlocked successfully.

So i went into the framework code which takes care of showing lock screen, unlock screen and handling the events on that. It is present
/frameworks/policies/base/phone/com/android/internal/policy/impl

If in case you are working in framework level code and looking for the exact place in the code, here it is.

KeyguardViewMediator.java

Line no 801:
Here i have written code, to notify the Notification service, when the screen is unlocked. It works fine.



public void keyguardDone(boolean authenticated, boolean wakeup) {
synchronized (this) {
EventLog.writeEvent(70000, 2);
if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
System.out.println("Keygaurd screen unlocked ");
mManager.notify("SCREEN_UNLOCKED"); // The code i added.
Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
msg.arg1 = wakeup ? 1 : 0;
mHandler.sendMessage(msg);

if (authenticated) {
mUpdateMonitor.clearFailedAttempts();
}

if (mExitSecureCallback != null) {
mExitSecureCallback.onKeyguardExitResult(authenticated);
mExitSecureCallback = null;

if (authenticated) {
// after succesfully exiting securely, no need to reshow
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
}
}
}
}



There are intents for when the screen is ON & OFF, but you can use that for screen unlocking.

- ACTION_SCREEN_ON
- ACTION_SCREEN_OFF.

I found in one of the forums which suggesting in round other way.

- Wait for ACTION_SCREEN_ON.
- (After screen is on,) Wait for ACTION_MAIN with category CATEGORY_HOME (Which launches the home screen) - This is probably what is sent after the phone gets unlocked.

Not sure this works. !! Check it out.

Friday, October 8, 2010

how to flip between two images and change images based on states of a widget

Lets us discuss about how do we do image switching with the help of ViewFlipper class. And also some tips about using item-selector for selecting images based on various states ( of a widget like button, imageview )

What we are gonna achieve now is, we have two images, which needs to flipped or toggled between them.

We have two images stop & refresh.
we need to toggle the stop / refresh button based on a button click.

1 - In the layout --> main.xml

I am going to add the two imageviews ( stop & refresh imageviews ) under a tag .



<ViewFlipper android:id="@+id/imageflipper"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<ImageView
android:layout_width="50dip"
android:layout_height="50dip"
android:src="@drawable/stop"/>

<ImageView
android:layout_width="50dip"
android:layout_height="50dip"
android:src="@drawable/refresh"/>

</ViewFlipper>



2 - Two most attributes for ViewFlipper class is setting In & Out animations when a view is switched from one to another and next one is the time taken to do switch the views. This is will come into picture if you had set the setAutoStart(true)



flipper=(ViewFlipper)findViewById(R.id.details);
flipper.setInAnimation(getApplicationContext(), R.anim.slide_right_in);
flipper.setOutAnimation(getApplicationContext(), R.anim.slide_left_out);
btn=(Button)findViewById(R.id.flip_me);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
flipper.showNext();
}
});


flipper.showNext() -> shows the next view in that ViewFlipper content. For us the initially when the screen is launched, Stop will be showed. When the button is clicked, it shows the refresh.
It circulates the two images for each button click.

Suppose, you had three imageviews in the <ViewFlipper> tag, each one will be showed next to next ( 1 to 2 to 3 ) again ( 3 to 1 to 2 to 3 ... ) on each button press.




In this example, i had set the imageview src. Which means you cannot change the image backgrounds, when clicked.

To achieve that I am gonna write a xml which handles the different states of images.( pressed, focused ) Basically this xml is a <selector>

More info http://developer.android.com/guide/topics/resources/available-resources.html

Note : Clicking the image wont flip the images in ViewFlipper.

Lets write a refresh_state.xml and put it in /drawable folder. [ keep the images refresh_pressed, refresh_default in drawable folder ]



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

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_pressed="true"
android:drawable="@drawable/refresh_pressed">
</item>

<item android:state_focused="true"
android:drawable="@drawable/refresh_default">
</item>

<item
android:drawable="@drawable/refresh_default">
</item>

</selector>



1 - android:state_pressed="true"
android:state_enabled="true"
This state implies that the widget is set Enabled (true ) and also pressed, it picks this image.

2 - android:state_pressed="true"
android:state_enabled="false"

This state implies that the widget is set Enabled (false ) ex. button is disabled like it is dimmed meaning no action for that,and if imageView is pressed, it picks this image.

3 - android:state_enabled="false"

This state implies that the widget is set Enabled (false ) ex. button is disabled like it is dimmed meaning no action for that, then it displays this image.

How to make the widget ( button, imageView etc ) to be disable or enabled in code.

this api sets the state ' state_enabled' to 'true' or 'false'

Imageview v = (ImageView) findViewById(R.id.refresh);
v.setEnabled(true);
v.setEnabled(false);

make changes in layout.xml file as below.

Remove the src settings and set the 'background' property to point the refresh_state.xml file created in above step.
- android:clickable="true" --> this has to be set otherwise, click events will not allowed for imageview



<ViewFlipper android:id="@+id/imageflipper"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >

<ImageView
android:layout_width="50dip"
android:layout_height="50dip"
android:background="@drawable/stop_state"
android:clickable="true"/>

<ImageView
android:layout_width="50dip"
android:layout_height="50dip"
android:background="@drawable/refresh_state"
android:clickable="true"/>

</ViewFlipper>

Monday, October 4, 2010

How to set height,width for activity or how to make the activity window look in desired size..

This post talks about

- how to display a activity of fixed width & height ?

- why the activity screen is of full screen ? how to reduce the size of activity size?


These were the questions which were running through my mind, when i first started implementing activities in android.

This applies not only to activities it is even for dialogs.How to position the dialog in the desired place in the screen.Sometime we want alert to be displayed on the top right, rather at the center of the screen as user might be reading something.

Lets work on it more to find the answers...!!


1 step - To make the activity to have a desired size rather than full screen, set the theme for your activity as Dialog like below.

android:theme="@android:style/Theme.Dialog"

In AndroidManifest.xml



<activity android:name="urldisplay"
android:label="@string/app_name"
android:windowSoftInputMode="stateAlwaysVisible|adjustPan"
android:theme="@android:style/Theme.Dialog">
</activity>


2 step - We need to get the window from dialog and set the layout attributes, then the window will be positioned accordingly in the screen. This can be done as below.



WindowManager.LayoutParams params = getWindow().getAttributes();
params.x = -100;
params.height = 70;
params.width = 1000;
params.y = -50;

this.getWindow().setAttributes(params);


here is the complete example.




layout.xml (main.xml)



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

<EditText android:id="@+id/textbox1"
android:hint="eg. pubs,restuarants "
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"/>

<Button android:id="@+id/press"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello"/>


</LinearLayout>


urldisplay.java



package com.android.urldisplay;

import android.widget.Button;
import android.widget.EditText;
import android.app.Activity;
import android.os.Bundle;
import android.view.WindowManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
import android.view.View;
import android.content.Context;
import android.net.Uri;
import android.app.PendingIntent;
import android.os.Handler;
import android.view.inputmethod.InputMethodManager;

public class urldisplay extends Activity
{
EditText nameText;
private static final int APP_ID = 0;
private NotificationManager mManager;
private Handler mHandler = new Handler();

private Runnable mShowInputMethodTask = new Runnable() {
public void run() {
showInputMethodForQuery();
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

WindowManager.LayoutParams params = getWindow().getAttributes();
params.x = -100;
params.height = 70;
params.width = 1000;
params.y = -50;

this.getWindow().setAttributes(params);
nameText = (EditText) findViewById(R.id.textbox1);

Context context;
context = getApplicationContext();
mManager = (NotificationManager) getSystemService(context.NOTIFICATION_SERVICE);

Button press = (Button)findViewById(R.id.press);

press.setOnClickListener(new Button.OnClickListener(){

public void onClick(View v) {

String url = nameText.getText().toString();
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "http://" + url;

Intent browserIntent = new Intent("android.intent.action.VIEW", Uri.parse(url));
browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mManager.notify(url);
startActivity(browserIntent);
finish();

}
});
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0);
}
}
protected void showInputMethodForQuery() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(nameText, 0);
}
}
}

Saturday, October 2, 2010

Dynamic layout changes with visibility property


When i started working on the layouts, i was in a situation where i need to hide a textbox based on checkbox selection. If checkbox selected hide the textbox and checkbox deselected show the textbox. That was my need.

there is a propery called VISIBILITY. It can be set a element on the layoutxml as below ( element can be a LinearLayout(viewgroup) or widgets like TextView,ImageView, etc )

" android:visible = "gone" or "visible" or "invisible"

- gone - Means the element will not be shown and its doesnt occupy space in the entire viewgroup.

- visible - THe element is shown. Visible to user.

- invisible - the element will not be shown, but it occupies empty space in for its width & height in the entire layout.

- in the code this propery can be set as

void setVisibility (int visibility)

- View.VISIBLE or View.INVISIBLE or View.GONE.

Let see an example.

layout.xml:
--------------------------


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/MainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="3dip"
android:orientation="vertical">
<RelativeLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip">

<ImageView android:id="@+id/favorite"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_alignParentLeft="true"
android:src="@drawable/favorite"/>

<TextView android:id="@+id/title1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="Local Guide"
android:textSize="25sp" />

<ImageView android:id="@+id/info"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_alignParentRight="true"
android:src="@drawable/info"/>

</RelativeLayout>

<TextView android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="25sp"
android:paddingTop="30dip"
android:text="Enter category :" />

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:orientation="horizontal">

<EditText android:id="@+id/categotytextbox"
android:hint="eg. pubs,restuarants "
android:layout_width="250dip"
android:layout_height="wrap_content"
android:singleLine="true"
android:visibility="gone"
android:ellipsize="marquee"
android:maxLines="1"/>

<ImageView android:id="@+id/search"
android:layout_marginLeft="20dip"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="@drawable/find"/>
</LinearLayout>

<Button android:id="@+id/categories"
android:layout_width="150dip"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="Choose categories"/>

<TextView android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="Enter Location :" />

<EditText android:id="@+id/locationtextbox"
android:hint="eg. Liverpool,uk "
android:layout_width="250dip"
android:layout_height="wrap_content"
android:maxLines="1"/>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="20dip"
android:id="@+id/locationLayout">

<CheckBox android:id="@+id/checkbox"
android:layout_width="40dp"
android:layout_height="40dp"
android:checked="true"/>

<TextView android:id="@+id/checkboxtext"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:paddingLeft="20dp"
android:layout_gravity="center_horizontal"
android:layout_centerHorizontal="true"
android:textSize="25sp"
android:text="Current Location" />

</LinearLayout>


</LinearLayout>





Main activity ( java file )
-------------------------------

On selection of checkbox, i dynamically show or hide the elements on above that.!!



locationCheckbox.setOnClickListener(new CheckBox.OnClickListener(){
public void onClick(View v) {
if(((CheckBox)v).isChecked())
{
TextView text1 = (TextView)findViewById(R.id.text1);
text1.setVisibility(View.GONE);
EditText locationbox = (EditText)findViewById(R.id.locationtextbox);
locationbox.setVisibility(View.GONE);
}
else
{ TextView text1 = (TextView)findViewById(R.id.text1);
text1.setVisibility(View.VISIBLE);
EditText locationbox = (EditText)findViewById(R.id.locationtextbox);
locationbox.setVisibility(View.VISIBLE);

}
}
});







import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;


public class WelcomeScreen extends Activity {

public final int CATEGORY_ID =0;
EditText categoryTextbox;
EditText locationTextbox;
Dialog dialog;
String category;
String location;
public final static int ACTIVITY_INVOKE = 0;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome);

categoryTextbox = (EditText)findViewById(R.id.categotytextbox);
locationTextbox = (EditText)findViewById(R.id.locationtextbox);
ImageView search = (ImageView)findViewById(R.id.search);
ImageView info = (ImageView) findViewById(R.id.info);
CheckBox locationCheckbox =(CheckBox)findViewById(R.id.checkbox);

search.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("categoryString", category);
location = locationTextbox.getText().toString();
intent.putExtra("locationString", location);
Bundle bun = new Bundle();
bun.putString("categoryString", category);
bun.putString("locationString", location);
intent.putExtras(bun);
intent.setClass(v.getContext(), results.class);
startActivity(intent);
}
});

info.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(v.getContext(), information.class);
startActivity(intent);
}
});

locationCheckbox.setOnClickListener(new CheckBox.OnClickListener(){
public void onClick(View v) {
if(((CheckBox)v).isChecked())
{
TextView text1 = (TextView)findViewById(R.id.text1);
text1.setVisibility(View.GONE);
EditText locationbox = (EditText)findViewById(R.id.locationtextbox);
locationbox.setVisibility(View.GONE);
}
else
{ TextView text1 = (TextView)findViewById(R.id.text1);
text1.setVisibility(View.VISIBLE);
EditText locationbox = (EditText)findViewById(R.id.locationtextbox);
locationbox.setVisibility(View.VISIBLE);

}
}
});
}
}