Tuesday, January 25, 2011

How to bring spinner at end of button using custom view

I was trying to implement a spinner at the end of button, to indicate user that some background connection to internet was going on to fetch some data.

First approach i took was to implement a Frame animation.

- Create a different images of spinner at different positions and then add a each images to animationDrawable instance and set the drawable to button using
'setCompoundDrawable()' api as defined below


http://developer.android.com/reference/android/widget/TextView.html#setCompoundDrawables(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable)

When needed to start/stop use start() and stop() apis and setCompoundDrawable(null,null,null,null) to hide the drawable.



package com.mani.spinner;

import android.app.Activity;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class spinner extends Activity {
/** Called when the activity is first created. */
Drawable spinnerAnimation;
Drawable spinnerBackground;
Button b1;
Button b2;
sample view;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
view = (sample)findViewById(R.id.sampleView);

spinnerAnimation = new AnimationDrawable();
spinnerAnimation.setBounds(0, 0, 20, 20);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a1);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a2);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a3);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a4);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a5);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a6);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a7);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a8);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a9);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a10);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a11);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

((AnimationDrawable)spinnerAnimation).setOneShot(false);

b1 = (Button)findViewById(R.id.button1);
b1.setCompoundDrawables(null, null, spinnerAnimation, null);
b1.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
b1.setCompoundDrawables(null, null, spinnerAnimation, null);
((Animatable)spinnerAnimation).start();

}
});
b2 = (Button)findViewById(R.id.button2);
b2.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

((Animatable)spinnerAnimation).stop();
b1.setCompoundDrawables(null, null, null, null);

}
});

}
}





More info on Frame animation you can check here :
http://iserveandroid.blogspot.com/2010/10/frame-animation-circular-spinner.html

I was thinking how to use one image and rotate it to bring the same effect of spinning. Then i tried this approach.

Second approach rotate animation resource

Spin.xml @res/anim folder.


xmlns:android="http://schemas.android.com/apk/res/android"
android:repeatCount="infinite"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="360" />



ImageView v1;
Animation mAnimation = AnimationUtils.loadAnimation(mContext, R.anim.spin);
v1.startAnimation(mAnimation);

Load the animation and apply to a image. But i could bring this effect to only the spinner image alone. Together with button and spinner @ end of button, i couldnt achieve using this.




Then i planned to create a custom view and create a button kinda of illusion and draw the spinner bitmap with different angles and create the effect like spinner @ end of button.

Third approach: Custom View

- Inherit from View class and over ride onDraw() method.
- Some special api's in canvas allows you to achieve the spinning of image.



canvas.drawRect(mButtonRect, mPaint);
canvas.drawText("More", 40,40, mPaint);
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();


- there is a option in canvas to 'save' and 'restore'. using these apis we can save the canvas state and do some manipulation to new canvas, place objects whereever needed and then do restore so that canvas contents before save is drawn without disturbed.

- Like in this, a rectangle is drawn (imagine a button ) and a text on the button is drawn at correct cordinates.
- Then 'save' the canvas and move the canvas to the 80% length of the screen(or width of the button) using 'translate' and rotate the canvas by certain angle then draw the spinner bitmap. It looks like the bitmap is drawn at the end of original canvas with rotated.

- When the angle is kept incremented using a runnable and invalidate() is called, then you would see like the image is rotated at the end...!!

-make the view class implement 'Runnable" and implement run as below to call onDraw() for every angle change.


public void run()
{
if(mAngle == 360)
mAngle=45;
else
mAngle+=45;
invalidate();
mHandler.postDelayed(this, 100);
}




main.xml:
----------


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Start"
/>
android:id="@+id/button2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Stop"
/>
android:id="@+id/image"
android:src="@drawable/spinner_white_48"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
android:id="@+id/sampleView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>




sample.java:

package com.mani.spinner;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

public class sample extends View implements Runnable{

Bitmap mSpinnerBitmap;
Paint mPaint;
Rect mSpinnerRect;
Rect mButtonRect;
int mButtonWidth;
int mButtonHeight;
int mSpinnerX;
int mSpinnerPivotX;
int mSpinnerPivotY;
int mSpinnerWidth;
int mSpinnerHeight;
boolean mSpinnerVisible;
Runnable mTask;
float mAngle=0;

Handler mHandler = new Handler();
String mButtonText="More";
public sample(Context context,AttributeSet attrs)
{
super(context,attrs);
mSpinnerBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.spinner_white_48);
mPaint = new Paint();
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);

Display display = ((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
mButtonWidth = display.getWidth();
mButtonHeight = 70;
mSpinnerHeight = mSpinnerWidth = mButtonHeight;
mSpinnerPivotX = mSpinnerPivotY = mSpinnerWidth /2;
mSpinnerX = (mButtonWidth-mButtonHeight);
mSpinnerRect = new Rect(0,0,mSpinnerWidth,mSpinnerHeight);
mButtonRect = new Rect(0,0,mButtonWidth,mButtonHeight);
}
public void setText(String text)
{
mButtonText = text;
}
public void setTextSize(int size)
{
mPaint.setTextSize(size);
}

public void setButtonColor(Color color)
{

}
public void start()
{
mHandler.post(this);
mSpinnerVisible = true;
}

public void stop()
{
mSpinnerVisible = false;
mHandler.removeCallbacks(this);
invalidate();
}

public void run()
{
if(mAngle == 360)
mAngle=45;
else
mAngle+=45;
invalidate();
mHandler.postDelayed(this, 100);
}

@Override
public void onDraw(Canvas canvas)
{

canvas.drawRect(mButtonRect, mPaint);
canvas.drawText("More", 40,40, mPaint);
if(mSpinnerVisible == true)
{
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();
}

}

}


Note:
If you would like to see how the canvas is rotated, create a visibleRect using view's height and width and draw a rectangle. Which will show you that entire canvas content is rotated.

Rect visibleRect;
visibleRect.set(0, 0, this.getWidth(), this.getHeight());

if(mSpinnerVisible == true)
{
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawRect(visibleRect, mPaint);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();
}

Thursday, January 20, 2011

Drag/Move a image in a custom view - part I

This post could be a first step of approach in moving a image in a view. With this approach you can start creating (base view and image movements) for a simple games like number puzzles, crosswords, pin ball.

1 - Create a class extends view. and override onDraw() and decide what are the contents you need to draw.

2 -To Keep moving a image in a view, we should be keep changing the x,y position of the image. This is the basic for image movement in a view

3 - In this example, a rectangle is drawn. And its position is hold in a variable ' Rect ImagePosition'

4 - When movement is detected 'ACTION_MOVE' event in 'OnTouchEvent()' function, deltaX is calcualted with the previous (x,y) and current moved (x,y) and a check is made like, whether , if the deltas are added to mImagePosition (top, left and bottom right ) is withing the visible screen. If 'yes', then deltax are added to 'Rect mImagePosition'. And invalidate() is called to draw() method to draw the rectangle. So it looks like the object is moving as when touched and moved along with the finger.


mImagePosition.left = mImagePosition.left + deltaX;
mImagePosition.top = mImagePosition.top + deltaY;
mImagePosition.right = mImagePosition.left + mImageWidth;
mImagePosition.bottom = mImagePosition.top + mImageHeight;
mImageRegion.set(mImagePosition);
prevX = positionX;
prevY = positionY;

invalidate();


Here is the complete code, i have tried out.

dragimage.java
----------------


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

public class dragimage extends Activity {

sample mView;
sample1 mView1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}
}


main.xml:
----------


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>




sample.java:
-------------


package com.mani.dragimage;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
public final class sample extends View{
Paint mPaint;
Rect mRect;
Bitmap bitmap;
private int mTouchSlop;
private int mTouchMode;
int mScreenHeight;
int mScreenWidth;
int prevX;
int prevY;
static final int TOUCH_MODE_TAP = 1;
static final int TOUCH_MODE_DOWN = 2;
final int mImageWidth = 100;
final int mImageHeight = 100;
Rect mImagePosition;
Region mImageRegion;
boolean canImageMove;

public sample(Context context,AttributeSet attrs)
{
super(context,attrs);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.chrome);
mPaint = new Paint();
mPaint.setTextSize(25);
mPaint.setColor(0xFF0000FF);
//Size for image
mImagePosition = new Rect(10,10,mImageWidth,mImageHeight);
mImageRegion = new Region();
mImageRegion.set(mImagePosition);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
Display display = (WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
mScreenHeight = display.getHeight();
mScreenWidth = display.getWidth();
canImageMove = false;
}


public boolean onTouchEvent(MotionEvent event)
{
int positionX = (int)event.getRawX();
int positionY = (int)event.getRawY();

switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
mTouchMode = TOUCH_MODE_DOWN;

if(mImageRegion.contains(positionX, positionY))
{
prevX = positionX;
prevY = positionY;
canImageMove = true;
}
}
break;

case MotionEvent.ACTION_MOVE:
{
if(canImageMove == true)
{
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int distY = Math.abs(positionY - prevY);
final int distX = Math.abs(positionX - prevX);

if (distX > mTouchSlop || distY > mTouchSlop)
{
int deltaX = positionX-prevX ;
int deltaY = positionY-prevY;
// Check if delta is added, is the rectangle is within the visible screen
if((mImagePosition.left+ deltaX) > 0 && ((mImagePosition.right +deltaX) < mScreenWidth ) && (mImagePosition.top +deltaY) >0 && ((mImagePosition.bottom+deltaY)))
{
// invalidate current position as we are moving...
mImagePosition.left = mImagePosition.left + deltaX;
mImagePosition.top = mImagePosition.top + deltaY;
mImagePosition.right = mImagePosition.left + mImageWidth;
mImagePosition.bottom = mImagePosition.top + mImageHeight;
mImageRegion.set(mImagePosition);
prevX = positionX;
prevY = positionY;

invalidate();
}
}
}
}
break;
case MotionEvent.ACTION_UP:
canImageMove = false;
break;
}
return true;
}

@Override
public void onDraw(Canvas canvas)
{
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);

// make the entire canvas white
paint.setColor(Color.CYAN);
Rect rect = new Rect(0,0,this.getWidth(),this.getHeight());
canvas.drawRect(mImagePosition, paint);
//canvas.drawBitmap(bitmap, null,mImagePosition, null);
}


}

Monday, January 17, 2011

Important framework structures/directories in android

Framework structure

Many would be working on android framework level to achieve something which is common across android system wide ( ex. it will be seen across all android applications like activity switching animations, status bar changes,any UI component changes )

I would like to share what are the important folder structures available and what are the important jar files created , how are they created , compilation sequences.

Below are the three main important jar files created in /out/target/generic/system/framework for make @ root level of AOSP......!!

1 - framework.jar
/framework/base --> Android.mk

Some of the important items which gets build as a part of this jar are

- all widgets, view groups, notification aidl...

2 - services.jar
/framework/base/services/java --> Android.mk

- All system level services are defined here, ex. statusBarService, NotificationManagerService,
- status bar view creation, designining is handled here.

3 - android.phone_policy.jar
/framework/base/policies/impl/phone -->Android.mk

4 - framework-res.apk
/framework/base/core/res/ -->Android.mk + AndroidManifest.xml

This is not a jar. An apk is created, which has a critical importance in framework. The resources present in framework are compiled and an intermediate R.java is created as a part of this apk compilation.

/out/target/common/obj/APPS/framework-res_intermediates/src/android/R.java
/out/target/common/obj/APPS/framework-res_intermediates/src/com/R.java

These R.java s are important while compiling rest of framework code. So if u take a look at Android.mk file present in framework/base, this intermediate R.java file is included while compilation.



fg-res-source-path := APPS/fg-res_intermediates/src
# $(fg-res-source-path)/com/fusiongarage/R.java
LOCAL_INTERMEDIATE_SOURCES := \
$(framework-res-source-path)/android/R.java \
$(framework-res-source-path)/android/Manifest.java \
$(framework-res-source-path)/com/android/internal/R.java \
$(fg-res-source-path)/com/fusiongarage/R.java


Further all services declaration, permission declarations, and some activities declarations are defined as a part of framework-res.apk's AndroidManifest.xml


Compilation process:

- When framework code is getting compiled there are intermediates folder created before creating the final Dex file(framework.jar,services.jar) which only can be run on android system.

For each compilation of Android.mk file there will be a intermediates folder created in 'out/target/common/obj/JAVA_LIBRARIES/'
If the android.mk file is defined to create java library out of it,it should have the below definition in it..
include $(BUILD_JAVA_LIBRARY)

Similarly for if an android.mk has the specification to build an package(apk) out of it, then its intermediates will be located in 'out/target/common/obj/APPS'

Below definition is required to create an apk from an android.mk.
include $(BUILD_PACKAGE)

When the logs are analysed when the compilation is going on, below are the flow i observed. And some hints from that.

- use ' mmm frameworks/base showcommands'
to see the logs what is going on for compilation sequences.

Dex file:

- All .java files will be compiled to .class filed and a jar file will be created first 'classes.jar'. From this classes.jar , classes.dex will be created using 'dx' executable.

out/host/linux-x86/bin/dx -JXms16M -JXmx1536M --dex --output=out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/classes.dex out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/classes.jar


javalib.jar
-

touch out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp//dummy
(cd out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/ && jar cf javalib.jar dummy)
zip -qd out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/javalib.jar dummy
rm out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp//dummy


aapt:

out/host/linux-x86/bin/aapt add -k
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/javalib.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/classes.dex
'out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.dex' as 'classes.dex'...
jar uf out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/javalib.jar -C frameworks/base preloaded-classes

Install: out/target/product/harmony/system/framework/framework.jar
acp:

out/host/linux-x86/bin/acp -fpt out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/javalib.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/tmp/framework.jar

Wednesday, January 12, 2011

How to access resources from other application (.apk)




We might be interested in accessing the resources of other application. Or inflate a particular view from other applications layout xml file.In this post, we will discuss more about how to achieve this.

1 - What ever we need to access (resources or layout), we need two main things from other application .
       
1 - R.java.
Required for you during compilation time.

2 - Application's context.
Resources needs to be inflated with the correct application's 'Context' instance.Else at runtime the prog will crash.

2 - The app1(.apk) needs to be installed on the device in order for you to create app1's context instance and then inflate the resources / layouts.

app1 :
------
Create a an android application with package (com.android.app1 ) which contains resources you want to expose to others.

AndroidManifest.xml:

Manifest file doesnt have any entry for activity or application tags. it just says, under which package the resources will be present.


package="com.android.app1"
android:versionCode="1"
android:versionName="1.0">




Keep the resources you want to expose in drawables. In this case i have kept the picture named 'pic1.jpg' .

app2:

1 - Create an android application with package name com.android.app2.

we need to include the com.android.app1.R.class into app2.

2 - Select project Right click -> Properties -> Java Build Path -> Libraries -> Add External class folder -> "Choose the app1/bin folder"
Now app2 can reference 'com.android.app1.R'

And create the context for app1.

Context otherAppContext = getApplicatoinContext().createPackageContext("com.android.app1", Context.CONTEXT_IGNORE_SECURITY);
Bitmap b1 = BitmapFactory.decodeResource(otherAppContext.getResources(),com.android.app1.R.drawable.pic1);


Use the bitmap from 'app1' to apply to a Imageview.

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


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
android:id="@+id/image1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/rajini"
/>
android:id="@+id/image2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>



app2.java:
-----------

package com.android.app2;


import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.widget.ImageView;


public class app2 extends Activity {
ImageView imgView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imgView = (ImageView)findViewById(R.id.image2);
try
{
Context otherAppContext = getApplicationContext().createPackageContext("com.android.app1", Context.CONTEXT_IGNORE_SECURITY);
Bitmap b1 = BitmapFactory.decodeResource(otherAppContext.getResources(),com.android.app1.R.drawable.pic1);
BitmapDrawable bitmapDrawable = new BitmapDrawable(b1);
imgView.setBackgroundDrawable(bitmapDrawable);
}
catch(Exception e)
{

}
}
}


How to start an activity if it is not see in launcher application list.

$ am start -n com.android.app2/com.android.app2.app2
Starting: Intent { cmp=com.android.app2/.app2 }

Monday, January 3, 2011

How to implement your own status bar view...!!

Some of you might be interested in replacing the android provided status bar with their own creative stuffs on top of the phone, instead the standard one provided by android.In this post i am going to explain how you can replace existing android's standard status bar view with your view.

Layout for android status bar is defined in
/frameworks/base/core/res/res/layout/status_bar.xml

The inflation part is handled in StatusBarService.java
/frameorks/base/services/java/com/android/server/status/StatusBarService.java

Line no 256 ( Roughly )

private void makeStatusBarView(Context context) {
Resources res = context.getResources();
mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order);
mRightIcons = new StatusBarIcon[mRightIconSlots.length];

ExpandedView expanded = (ExpandedView)View.inflate(context,
com.android.internal.R.layout.status_bar_expanded, null);
expanded.mService = this;
StatusBarView sb = (StatusBarView)View.inflate(context,
com.android.internal.R.layout.status_bar, null);

...............................
...............................
...............................
}


The place where the status bar view added is

public void systemReady() {
final StatusBarView view = mStatusBarView;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
view.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height),
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING,
mPixelFormat);
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.setTitle("StatusBar");
lp.windowAnimations = R.style.Animation_StatusBar;

WindowManagerImpl.getDefault().addView(view, lp);
}


The status bar view which was inflated earlier is added to WindowManager.
WindowManagerImpl.getDefault().addView(view, lp);

In order for your view to be shown, simple, you need to bring in your 'View' instance and add it here at this point. DONE...!!

So simple isn't it...!!

But, well where will you keep your view handling/creating (i mean the .java file), how will you show default status bar notifications( like antenna, wifi, bluetooth ) every thing which a status bar shows now.

1 - Create a folder in /frameworks/base named 'mystatus'
2 - Create java and res folders and create folders inside 'java' as per package names and keep the resources, layouts files you need under res corresponding folders ( drawable-hdpi , layouts )


java->com->mani->mystatus
res->drawable-hdpi
res->drawable-mdpi
res->layouts
res->assets
res->anim ( if any required )


3 - In this way you have all your independent view creation/public exposed apis present in a seperate folder in framework.

MyStatusBarView.java



package com.mani.mystatus;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
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.drawable.LevelListDrawable;
import android.graphics.drawable.AnimationDrawable;

public final class MyStatusBarView extends LinearLayout{

private TextView batterytext;
private TextView batterylevel;
private TextView batterypercentage;

public MyStatusBarView(Context context, AttributeSet attrs){
super(context, attrs);

}

public MyStatusBarView(Context context){
super(context);

batterytext = new TextView(context);
batterytext.setTextSize(20);
batterytext.setPadding(7,0,0,0);
batterytext.setTextColor(Color.GRAY);
batterytext.setText("MANI - My status bar");

batterylevel = new TextView(context);
batterylevel.setTextSize(22);
batterylevel.setPadding(7,0,0,0);
batterylevel.setTextColor(Color.RED);

batterypercentage = new TextView(context);
batterypercentage.setTextSize(18);
batterypercentage.setPadding(2,0,0,0);
batterypercentage.setText("%");
batterypercentage.setTextColor(Color.RED);

LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);
LinearLayout.LayoutParams levelParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);
LinearLayout.LayoutParams percentageParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);

batterytext.setLayoutParams(textParams);
batterylevel.setLayoutParams(levelParams);
batterypercentage.setLayoutParams(percentageParams);

this.setBackgroundColor(Color.BLUE);

this.addView(batterytext);
this.addView(batterylevel);
this.addView(batterypercentage);

}


public void setBatteryLevel(int level)
{
String blevel = "";
blevel+= level;
batterylevel.setText(blevel);

}

}



Changes required in StatusBarService.java
------------------------------------------

These are the four steps you need to implement.



1- Import the view class
import com.mani.mystatus.MyStatusBarView;

2 - Declare a global variable to MyStatusBarView
MyStatusBarView myStatusBarView;

3 - Create a instance (NOTE: We are passing context obtained in StatusBarService)
myStatusBarView = new MyStatusBarView(mContext);

4 - Add the view instance to WindowManager
WindowManagerImpl.getDefault().addView(myStatusBarView, lp);


I am going to show you how to display battery percentage in the custom view. (later you can implement what are all the details you require from statusbarservice and send it back to your view using public exposed apis).

So i am going to expose a public function in MyStatusBarView class to get the battery percent and display it.
public void setBatteryLevel(int level);

In StatusBarService.java:

public void updateBatteryStats(IconData batteryData)
{
myStatusBarView.setBatteryLevel(batteryData.iconLevel);
}


In order for the framework compilation to pick up java files from 'mystatus' folder, add a entry in the file

/build/core/pathmap.mk as below.


1 - pathmap.mk /build --> Add the folder.
FRAMEWORKS_BASE_SUBDIRS := \
$(addsuffix /java, \
core \
mystatus \
graphics \
location \
media \
opengl \
sax \
telephony \
wifi \
vpn \
keystore \
)


Resources usuage:

We might be interested in using resources ( like images, layouts) in status bar view. Include your resources in res folder. Now i have two questions for you

1 - We need to have R.java file for compilation of java files in 'mystatus' (if in case if uses import com.mani.mystatus.R ).How we import these ?
2 - We need these resources to be brought to the devices. How we do this ?

For the first question

Include a entry in base/android.mk

This where framework-res R files are included before compilation.
(roughly line no 194 )

LOCAL_INTERMEDIATE_SOURCES := \
$(framework-res-source-path)/android/R.java \
$(framework-res-source-path)/android/Manifest.java \
$(framework-res-source-path)/com/android/internal/R.java \

So we need to include our newly created R.java file to this path.


mystatus-source-path := APPS/mystatus-res_intermediates/src

LOCAL_INTERMEDIATE_SOURCES := \
$(framework-res-source-path)/android/R.java \
$(framework-res-source-path)/android/Manifest.java \
$(framework-res-source-path)/com/android/internal/R.java \
$(mystatus-source-path)/com/mani/mystatus/R.java


For second question

When R.java files will be created ??. We need to compile the resources directory.
A resources directory will be compiled only if the compilation is for android application. ( i.e we need to create a apk out of it to see the R.java file generated)

How do we make 'mystatus' a application. ( No activity is present but that is okay )
Keep a AndroidManifest.xml under a res folder and a 'android.mk' file.

AndroidManifest.xml:









This manifest file basically tells the Resources will be under the package name 'com.mani.mystatus'

Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_STATIC_JAVA_LIBRARIES := \
android-common

LOCAL_PACKAGE_NAME := mystatus-res

# Install this alongside the libraries.
LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)

# Create package-export.apk, which other packages can use to get
# PRODUCT-agnostic resource data like IDs and type definitions.
LOCAL_EXPORT_PACKAGE_RESOURCES := true

include $(BUILD_PACKAGE)



Android.mk file is required for you to compile the subsystem from root directory.

For more understanding of android build process, what is going on when apk is built, please check below link.

http://www.alittlemadness.com/2010/06/07/understanding-the-android-build-process/
# make mystatus-res (or) # make framework/base/mystatus/res/

'mystatus-res.apk' will be created and kept in
out/target/product/generic/system/framework

You need to push this apk also.

In order for the 'mystatus' folder to be compiled you need to compile the base.
mmm frameworks/base

out/target/product/generic/system/framework/framework.jar

framework.jar contains the dex files of 'mystatus' java files.

In order to compile the status bar service changes, compile the services part.
mmm frameworks/base/services/java

out/target/product/generic/system/framework/services.jar

services.jar will have the changes/ recent modified changes in statusbarservice.java.

So totally three files files needs to be pused the device.

adb -d remount
adb -d push out/target/product/generic/system/framework/services.jar /system/framework
adb -d push out/target/product/generic/system/framework/framework.jar /system/framework
adb -d push out/target/product/generic/system/framework/mystatus-res.apk /system/framework


For safer side, push the entire framework contents to your device.
adb -d push out/target/product/generic/system/framework/ /system/framework

So now when the system boots up, you would be seeing your own status bar view (MyStatusBarView).

Enjoy the work...!!

Snapshots for your reference:
This was tried out on vanilla android 2.2



Saturday, January 1, 2011

15-Puzzle


I have implemented a 15-number puzzle game. It is present in android market for free.Please use it and give me your valuable comments.

On the phone when searching for '15 puzzle' in market place, you would see one result containing my name and this application icon.That is mine...:)

More info about the game please go through this wiki
http://en.wikipedia.org/wiki/Fifteen_puzzle

Source code is available in the following google project hosting
http://code.google.com/p/15-number-puzzle

Some screenshots for you :