Wednesday, November 30, 2011

how to write app Widgets - Home screen widgets ? How to implement different sized home screen widgets?

Recently i gave a presentation in google office singapore for gtug-ph-sg android community about Layouts and Home screen widgets.

http://gtug-ph-sg.blogspot.com/2011/11/android-talk-update.html

So i wished to share more details about how to write home screen widgets.
Also when i attended a google dev talk here happened on nov27th, the key steps to keep a user engaged in keeping your application is

1 - Write home screen widget and keep the user bsy in refreshing the data on widget
2 - Push notifications then and there so that user has not forgotten your application.

So we home screen widget is one way you can keep user bsy with your application. Lets go into the details.

* What is an AppWidget ?

* AppWidget framework

* Steps for creating an AppWidget

* Example demonstrating widget showing google static map of location as home screen widget.


AppWidget:

- AppWidget is a android techinical terminology for "Home screen widgets". What is said in developers guide is "App Widgets are miniature application views that can be embedded in other applications (such as the Home screen) and receive periodic updates."
-AppWidget is a representation of an Android application on the home screen. The application can decide, what representative information it publishes on the home screen



- Home screen widgets are quick glimpse of the fully functional apps like calendar,music,weather applicaiton. Users can quickly get live data from an application.Live data i mean, widgets can keep refreshing the data based on location,time basis, or per schedule input from user.

- Widgets let users interact with the widgets like slide the images like in gallery, scroll the weather data, play,pause songs..etc.






RemoteViews and Launcher:

Before diving into how to implement app widgets, we should be aware of how the appWidgets mechanism works and what are remoteviews ?

How does Launcher (i.e Home screen provider) able to show a view created by another application in its UI ? Functionality and design of the widget is defined by one application but UI of the widget is hosted in another application (Launcher). What would help us achieving this ? Basically we need an IPC mechanism here. Inter process communication. One process sending the UI data to another process Launcher to display in it.

Remoteviews helps in solving this and is the key behind appwidget framework. Remoteviews are parcelable data strucutre that holds information about a view hierarchy and can be transferred from one process to another. Any process can recieve this RemoteView instance via IPC and get a "View" instance from it and add to its Layout and be part of receving process. The creator of remoteview can define actions for the elements in remoteview like what should happen when a button is pressed. Receiving process cannot change these properties, they can only get a view instance and host it.

So who sends remoteviews and how does Launcher communicates with creator.

- AppWidgets framework includes implementing AppWidgetProvider class which is a broadcast receiver which would receive events of when an AppWidget is added to homescreen or deleted from homescreen.

- Launcher is the guy who sends these broadcasts when the appWidgets are added to the home screen with an AppWidgetID

- AppWidgetProvider then sends an RemoteView with the AppWidgetId, which would be then be received by Launcher and Launcher updates the corresponding appWidget with that remoteView.




AppWidget FrameWork

Now lets see the four necessary things involved in creating appWidgets.

* App Widget Provider Metadata XML file

* AppWidgetProvider class

* View layout

* App Widget configuration Activity (Optional)


App Widget Provider Metadata XML file

* Describes the metadata for an App Widget, such as minimum width, minimum height, update frequency, the AppWidgetProvider class.
* It also references App widget's layout file
* This should be defined in res/xml folder.

AppWidgetProvider class

* Defines the basic methods that allow you to programmatically interface with the App Widget, based on broadcast events. (AppWidgetProvider class is a child class of BroadcastReceiver class.)
* Through it, you will receive broadcasts when the ApWidget is updated, enabled, disabled and deleted

View layout

* Intial layout to be displayed when the appWidget is added to the homescreen. It is defined in metadata XML file.

App Widget configuration Activity

* This is an optional activity which users can define to show to the users before adding the appWidget to homescreen.Usually useful in collecting some values required for your appWidget settings.

Building an AppWidget

* Declare an AppWidgetProvider in the Manifestfile

* Create the App Widget Provider Info Metadata XML file

* Create the App Widget Layout XML file

* Write the AppWidgetProvider Class

1 - Declare AppWidgetProvider in Manifest




android:resource="@xml/example_appwidget_info"/>


2 - Create App Widget Provider Info Metadata XML file

xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"  --> Every one day
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure" >


Some of the attributes are added newly in android 3.0
previewImage --> Added in android 3.0
android:previewImage="@drawable/preview"

More details take look at http://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html

3 - Create App Widget Layout

* App Widget layouts are based on RemoteViews,which do not support every kind of layout or view widget.

* A RemoteViews object (and, consequently, an App Widget) can support the following layouts and Widget classes
FrameLayout, LinearLayout, RelativeLayoyt
AnalogClock, Button, Chronometer, ImageButton,
ImageView, ProgressBar, TextView

* Android 3.0 supports additional widgets
ListView
GridView
StackView
AdapterViewFlipper

4 - Write AppWidgetProvider Class

* The AppWidgetProvider class extends BroadcastReceiver to handle the App Widget broadcasts. The AppWidgetProvider receives only the event broadcasts that are relevant to this App Widget, such as when the App Widget is updated, deleted, enabled, and disabled.

Methods to override

onUpdate(Context, AppWidgetManager, int[]) - called
when each App Widget is added to a host (unless you use a configuration Activity), Typically the onlymethod that needs to be present

onDeleted(Context, int[])

onEnabled(Context)

onDisabled(Context)

onReceive(Context, Intent)

Screenshot showing demo of adding Digital and analog clock in Home screen:





AppWidget provider - onUpdate

There are certain few points about AppWidget Provider behaviour we need to know.

* First is , android:updatePeriodMillis="86400000" which defines the frequency when the appWidget will be updates. Meaning on this schedule, onUpdate on AppWidget Provider class will be called. The restriction with this timing is the minimum period is 30mins. You cannot give 10000 and except the onUpdate() to be called every 10 secs. Minimum time period is 30 mins.

* Because AppWidgetProvider is an extension of BroadcastReceiver, your process is not guaranteed to keep running after the callback methods returns i.e onUpdate,onEnabled.
So in case, you need to perform some network communication to fetch some data

* Consider starting a Service in the onUpdate() method. And delegate the network communication work to a asynchronous task and update the widget with the result.

* To update an appWidget all you need to know is AppWidgetId and have AppWidgetManager instance. So ideally pass all the appWidgetIds to "service" i.e whenever onUpdate() is triggered call startService() with appWidgetIds as intent data and ask the service to update the widgets with data fetched from network.
appWidgetManager.updateAppWidget(appWidgetId, views);

Now how to do we change the updatePeriodMillis to trigger onUpdate before 30mins.

* Use AlarmManager to update.In onUpdate() when the first time appWidget is added, set a setRepeating alarm for the schedule you wish and pass an pendingIndent to launch the service. In service you can update the appWidgets with appWidgetids and remoteview.

final AlarmManager m = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 
final Intent intent = new Intent(context, MyService.class);  
if (service == null)  
{  
 service = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 
}  
m.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1000 * 60, service); 


More details take a look at this post
http://www.parallelrealities.co.uk/2011/09/using-alarmmanager-for-updating-android.html

Multiple sized widgets :

- How do we support multiple sized widgets ? 4x1,4x2,2x1 there are maximum of 4 rows and 4 columns. 4x4 in handsets and tablets support upto 8x7.
Here is one of the way you can achieve this.

- Declare as many AppWidgetProviderInfo metafiles required
2x2 :
android:minWidth="147dp"
android:minHeight="142dp"
4x2 :
android:minWidth="294dp"
android:minHeight="142dp"

- Define corresponding Appwidget classes.

- Declare corresponding AppWidget Broadcast receivers in AndroidManifest.xml


For more details on the cells calculation, minWidth,minHeight please chk in this link.
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html

Snapshot showing four types of appWidgets for the demo application



Things to keep in mind

* Frequency of update should not be high. ex. every 5 mins.It will reduce the battery life of the phone.

* Handled badly your widget could end up making the phone completely unresponsive. Pushing updates to onscreen widgets is a somewhat costly process.

* Avoid nesting of layouts within a layout.

* Give proper padding. As it might collide with adjacent widget. No boundary seperation would be seen.

* Use a nine patch image as background to support multiple sized widgets

* Offload any webrequest through a service to avoid ANRs.


Demo example of widget showing google static map of location as home screen widget.

Quick points of implementation.

* onEnabled() -> Start a service

* onUpdate() -> Get the appWidgetId and appWidgetType and pass to the service.

* onDeleted() -> Get the appWidgetId and pass to the service.

* Service -> Listens for movement change using PhoneStateListener and fetches current location using GPS or Wifi and downloads the static map as Bitmap  and updates the widgets 2x2 and 4x2.

I am not giving in detail explanation of the code. Please go through this block diagram which will give you high level overview. You can go through the code for more details Any queries u can drop me a comment..!!

Implementation overview:







http://code.google.com/apis/maps/documentation/staticmaps/

Demo code:
The entire source code for the demo can be downloaded from the below link.
http://www.4shared.com/get/_FeDVgpD/HomescreenWidget.html

Sunday, November 20, 2011

LocalGuide - A map application with home screen widget.

Spent a bit of my free time in finishing a app named localguide..I have uploaded in market....!! It is there in OVI store as well.( it was quite old).

if u like the description and preview ...Please buy it :) and give ur feedbacks/comments :) if any improvements i will make sure ur issues are addressed over updates...!!

https://market.android.com/details?id=com.mani.localguide

http://www.youtube.com/watch?v=4-C_rK2lrow&feature=feedu

And if u felt useful... plss fwd to ur friends...!!

Description:
How many times we get stuck in a new place and call our friends to get directions to pubs, restaurants nearby and still be in an endless search.
Localguide puts an end to all this endless searches.

* LocalGuide takes two short keywords and directs you to the place you want to go with detailed map directions.
* It provides you the complete addresses, phone numbers, maps location. It identifies your location using GPS or Wifi.
* Make a call instantly.
* Send SMS/message the result to your friends right away.
* Update twitter,facebook status with the result informing (where you are presently ).
* Get directions to the place with neat and simple step by step instructions with clear paths shown using google map view.
* Bookmark your favourite Places and store them for fast consultation when in offline usage

A LITTLE ANIMATIONS are added during screen transitions.

Home Screen Widgets:

Localguide provides one of the interesting additional features is home screen widgets which has

* Support for 4x1 and 4x2 resolutions. Please see the previews.
* Provides instant results of your interested categories around your current location in home screen.
* Widgets has the capability to keep listening for your movement and detects the location automatically and refreshes the results.
* If in case you dont want to scan for your location movement, you quickly turn of the MOVEMENT DETECTION button on the widget.
* Multiple widgets can be added to display results for different categories.
EX: One widgets collects results for restuarants, one collects for theatres.
* Widgets provides an one button click option for calling and sharing through sms.
* Also provided favorite button which takes you to the favorites page to quickly get to your favorite destination.

Widgets are handly and quite useful. 4x1 and 4x2 gives you an option to keep your widgets compact at necessary place in workspace.

Any improvements pls mail me - smanikandan14@gmail.com











Friday, July 8, 2011

How to perform entry animations for listview.

From my work related learnings, i would like to share the three ways of doing entry animation for listview elements.


1 - LayoutAnimation Controller
This controller is viewgroup animation controller means, it will apply animation to each of the child added to a view group. When this controller is set to the listview
it animates each listviews items. So that you can have entry animation for listview.

But the drawback is this controller will apply the animation to the listview items only for very first time this layout is drawn/shown. The next time when you do
hide / show the layout, you cannot expect the listview to have its element animated.

2 - So the second approach i tried is over riding the onvisibilitychanged() api of Layout class where your listview is added.check for visibility,if someone made the layout visible then this the right place to perform the animation on each views of listview. Use getChildAt() api to get the listview items and start a animation on each view. So you get a entry animation for listview whenever, this layout is made visible. You can also write a exit animation and start when visibility == INVISIBLE


protected void onVisibilityChanged (View changedView, int visibility)
{
super.onVisibilityChanged(changedView,visibility);
if(visibility == View.VISIBLE) {
startEnterAnimation();
}
}

public void startEnterAnimation() {

Animation animation;
int offsetTime=0;
for(int i=0;i View view = list.getChildAt(i);
animation = new AlphaAnimation(0.0f,1.0f);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset( i * 100);

if(view != null)
{
view.startAnimation(animation);
}
}
}


There is also a limitation in the second approach. Scenario is, what happens before u perform the animation on the listview,there is a change in the data, where in you need to call the notifydatasetchanged api, then u wanted to start the entry animation.
But by the time you start performing the entry animation, notifydatasetchagned api would have layouted out the listview elements.

So we need to start the animation as and when listview items are drawn on the screen. how do we do this ??

Third approach solves this issue.

3 - In the adapter's getview() api, as and when we return the convertView to the framework, start the animation that instance by checiking for a flag,
which will be set to true for the first time listview is shown. Approach looks good, but how do u know when the listview has finished calling all its getview
and drawn its child so that you can make the flag false ??. Here is the solution i have tried, as soon as notifydatasetchanged api is called,

UI thread will have tasks in its messagequeue to perform getview for the count returned by getCount() adapter.so after notifydatasetchanged api is called,
get the handler of listview(which is nothing but UI thread handler) and post a runnable to make the boolean flag false, So that this runnable will be
executed after all the getview() calls are finished for all its child elements. So We know when to make the boolean false at the right instance.


protected void onVisibilityChanged (View changedView, int visibility)
{
super.onVisibilityChanged(changedView,visibility);
if(visibility == View.VISIBLE) {
// Suppose your data is upadted.
mListAdapter.notifyDataSetChanged();
isInitialLayout = true;
listview.getHandler().post(new Runnable() {
public void run() {
isInitialLayout = false;
}
});
}
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.listview_item, null);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.title.setText(mData.get(position));

/* Start the animation for this item for the position */
if(isInitialLayout == true ) {
Animation animation = new AlphaAnimation(0.0f,1.0f);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset(position * 100);
convertView.startAnimation(animation);
}

return convertView;
}


If there are any other ways of performing the entry animation for a listview in a layout, please let me/everybody know :)

Friday, June 24, 2011

How to calculate lsitview's total element's height

Sometimes we would be interested in finding height of the listview including all the child's height.(including the child which are not visible )

Usually the api listview.getChildCount() returns the count of number of elements which can be seen in the listview's height. But what i am insisting now is finding the height of all the childs of listview a collective height.

we will see how can we find the collective height of all child.

- Get the adapter instance from the listview.
- Get the count of adapter.
- And use adapter's getView(int position,View view,ViewGroup parent) api to get the view instance of all child
elements.
- Then use the measure api of View to measure the height of the view as below. I used here UNSPECIFIED to find out how big the view is.
- This api measures the widht and height. And use the getMeasuredHeight() api to find
the Measured Height.

private int getTotalHeightofListView() {
ListAdapter mAdapter = listview.getAdapter();
int listviewElementsheight = 0;
for(int i =0;i View mView = mAdapter.getView(i, null, listview);
mView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
listviewElementsheight+= mView.getMeasuredHeight();
}
return listviewElementsheight;
}


For additional information on MeasureSpec, taken from
http://developer.android.com/reference/android/view/View.html

MeasureSpecs are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:

UNSPECIFIED: This is used by a parent to determine the desired dimension of a child view. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child view wants to be given a width of 240 pixels.
EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
AT_MOST: This is used by the parent to impose a maximum size on the child. The child must gurantee that it and all of its descendants will fit within this size."


Sunday, June 12, 2011

How to perform exit animation for listview

Some would like to perform animations on list elements when listview is being made hidden or when listview is set to View.INVISIBLE, View.GONE.This is what i term it as "Exit animation of listview."


- listview has an api getChildCount() this will tell you how many number of list view elements are visible on screen and 'getChildAt(int)' api will give you the listview elements View instance.
- Get the childcount and iterate through the loop and get each child view instance and start a animation on each views, you will see a exit animation performed on it.


Below example shows how to perform alpha animation from 1 to 0, disappearing elements one by one from top to bottom.

public void startExitAnimation() {

Animation animation;
listcount = gridview.getChildCount();
int offsetTime=0;
animatedcount = 0;
for(int i=0;i {
View view = gridview.getChildAt(i);
animation = new AlphaAnimation(1.0f,0.0f);
animation.setAnimationListener(mExitAnimationListener);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset( i * 100);

if(view != null)
{
view.startAnimation(animation);
}
}
}

Monday, June 6, 2011

How to detect which is the current top activity.

Here is one of the way you can detect


public boolean whatIsCurrentActivity()
{
ActivityManager am = (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
List ActivityManager.RunningTaskInfo taskInfo = am.getRunningTasks(1);
if(taskInfo != null ){
System.out.println("Top activity - Package name of the process is "+taskInfo.get(0).topActivity.getPackageName() );
}

Wednesday, April 27, 2011

Inent-filter to listen for internet/data connectivity status

When designing home screen widgets which tries to talk to cloud, we might be interested in knowing internet connectivity status of device. Suppoese when adding widget, data connectivity might not be available, but your would expect your widget to start collecting data, as and when device is connected to internet.

1- Check for internet connectivity / data connectivity.



private boolean checkInternetConnection() {

ConnectivityManager conMgr = (ConnectivityManager) mContext.getSystemService (mContext.CONNECTIVITY_SERVICE);

// ARE WE CONNECTED TO THE NET

if (conMgr.getActiveNetworkInfo() != null
&& conMgr.getActiveNetworkInfo().isAvailable()
&& conMgr.getActiveNetworkInfo().isConnected()) {
return true;
} else {
return false;
}

}


2 - To receive for Internet Connectivity dropped / up, listen for
'ConnectivityManager.CONNECTIVITY_ACTION' action.

IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(mNetworkStateIntentReceiver, filter);


3 - Whenever there is a change in connectivity i.e it could be either data connection is connected or disconnected, you will receive this event as broadcast, so in onreceive of the broadcast receiver, please check for internetconnect connection and decide whether internet is up or down.


BroadcastReceiver mNetworkStateIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
ConnectivityManager.CONNECTIVITY_ACTION)) {

NetworkInfo info = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO);
String typeName = info.getTypeName();
String subtypeName = info.getSubtypeName();
System.out.println("Network is up ******** "+typeName+":::"+subtypeName);

if( checkInternetConnection()== true )
{
"Decide your code logic here...."
}
}
}

};

Wednesday, April 13, 2011

How to dismiss your non-modal dialog, when touched outside dialog region

When you implement your dialog as non-modal dialog, means when your dialog is shown, you can interact with other elements on the screen, in such case, you might be interested to dismiss the dialog, when user touches/press/interacts with other elments on the screen. The following steps will help you to reeceive for the touch events outside your non-modal dialog.

1 - Set the flag-FLAG_NOT_TOUCH_MODAL for your dialog's window attribute

Window window = this.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);

2 - Add another flag to windows properties,, FLAG_WATCH_OUTSIDE_TOUCH - this one is for dialog to receive touch event outside its visible region.

3 - Override onTouchEvent() of dialog and check for action type. if the action type is
'MotionEvent.ACTION_OUTSIDE' means, user is interacting outside the dialog region. So in this case, you can dimiss your dialog or decide what you wanted to perform.

public boolean onTouchEvent(MotionEvent event)
{

if(event.getAction() == MotionEvent.ACTION_OUTSIDE){
System.out.println("TOuch outside the dialog ******************** ");
this.dismiss();
}
return false;
}

Monday, March 28, 2011

how to launch pending intent

Pending Intent:

An application can create a Pending Intent, which is nothing but an intent and action to it, so that it can passed to some other applications which can execute this pendingintent as if original application executes it.

PendingIntents can be created of three types

getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int),
getService(Context, int, Intent, int);


You can create a pending intent to launch an activity, or service, broadcast an intent.

Let us see how to create an pendingIntent which can launch an activity and see once you have pendingIntent, how can you launch make operation on the intent.

- Create a two activities in your android project.
- One activity create an pendingIntent through 'getActivity()' api

PendingIntent pendingIntent;
Intent intent = new Intent();
intent.setClass(mContext,activity2.class);
pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

- pass activity2 as setClass parameter to intent and create the pendingIntent.
- In activty1, for button click action, use the pendingIntent instance to perform the operation on it using 'send()' api.

Intent intent = new Intent();
try {
pendingIntent.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here. Just log the exception message.
System.out.println( "Sending contentIntent failed: " );
}


activity1.java
---------------

package com.mani.pending;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;


public class activity extends Activity {
Button b1;
PendingIntent pendingIntent;
Context mContext;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = this.getApplicationContext();
b1 = (Button) findViewById(R.id.button);
Intent intent = new Intent();
intent.setClass(mContext,activity2.class);
pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

b1.setOnClickListener( new View.OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
try {
pendingIntent.send(mContext, 0, intent);

} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here. Just log the exception message.
System.out.println( "Sending contentIntent failed: " );
}
}
});

}
}


activity2.java
----------------


package com.mani.pending;

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

public class activity2 extends Activity{

TextView v1;
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.main);
v1 = (TextView)findViewById(R.id.text);
v1.setTextSize(30);
v1.setText("Welcome to pendinIntent");
}

}


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 :