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



28 comments:

  1. Hi Mani,
    I just tried out what you have posted, it works fine. I tried adding a icon on the fly on mystatusbarview.java, placed the icon image in drawable of the mystatus/res/drawable , this compiles fine. When I try it on my device , it throws the below error

    'android.content.res.Resources$NotFoundException: Resource ID #0x7f020001
    E/AndroidRuntime( 1370): at android.content.res.Resources.getValue(Resources.java:891)
    E/AndroidRuntime( 1370): at android.content.res.Resources.getDrawable(Resources.java:579)
    E/AndroidRuntime( 1370): at android.view.View.setBackgroundResource(View.java:7186)
    E/AndroidRuntime( 1370): at com.mist.customstatus.CustomStatusBarView.setVolumeLealarm_release: clear alarm, pending 0
    '

    In framework/base/Android.mk , I have added path of the this new status bar R.java file.

    Do you have any idea what could be going wrong?

    REgards
    Krt

    ReplyDelete
  2. Hi...Kitty...!!

    The problem could be u might not have used the right 'context' instance to pick up the Resources.

    What i mean is,, you need to create a 'context' from the package where mystatus folder is present and use that context to inflate the view from mystatus-res.apk

    MystatusContext = context.createPackageContext("com.frameworks.mystatus", Context.CONTEXT_IGNORE_SECURITY);

    in mystatus-res.apk --> AndroidManifest.xml

    check what is the package name you have specified. Please use that package name and get a context and access the resources from this context. it should work fine.

    Any issues, pls revert back to me ..!!

    Thanks,
    mani

    ReplyDelete
  3. Hi Mani,

    I have not tried modifying Android source yet, can you please tell me, steps to build the modified source.

    Also tell me, whether we can test the modified source with emulator? if so, how to do it?

    Thanks,
    Swathi

    ReplyDelete
  4. Swathi..

    You can launch the emulator from AOSP (Source code) which can reflect the changes made ins status bar.

    For compiling source code.

    1 # . ./build/envsetup.sh
    2 # lunch 1
    3 # make -j4

    Once the compilation is successfull (without any changes made) start making the changes as mentioned above and do "make -j4" again.

    Once it is successfull, go to
    #out/target/product/generic/system

    You would see three img files created. system.img,boot.img,recover.img.

    Then launch the emulator as like this.
    #emulator -skin 1200x500.

    Now you would see the emualtor having the status bar changes.

    Thanks,
    Mani




    Thanks,
    Mani

    ReplyDelete
  5. Hello,

    Your method is very usefull. I have create a complete moded status bar and he work fine on emulator.

    Now I want put it on galaxy tab! I have succed for services.odex and android.policy.odex with this method : http://www.drakaz.com/forum/viewtopic.php?pid=1002#p1002

    Now I try to push mystatus-res.apk in tablet, but this create lots of errors about framework ressources. I think that it's signing problem, do you have an idea? Do you think that I could edit framework-res.apk?

    Thanks

    ReplyDelete
  6. This is a beautiful post.
    It covers actually many concepts.
    Thnx :)

    ReplyDelete
  7. @Anonymous:

    The method i was explaining will work on emulator or on the device which u are manufacturing and u have full access.. i mean root access..!!
    Coz u need to push the mystatus-res.apk into /system/app, which basically u cannot do in the other devices like Galaxy tab. Plus u need to replace the "services.jar" in the system. So this would be useful if you are having your own device and code base for it. (BSP).

    thanks,
    Mani

    ReplyDelete
  8. Thanks it's work. I have my custom status bar on rooted galaxy tab.

    regards

    ReplyDelete
  9. Hi there,
    great post, thanks for it.

    I have little problem, i'm trying to achieve similar thing but in frameworks/opt. I've created there dir with my package name and done everything analogous.
    During compilation i got following error:

    error: cannot read: out/target/common/obj/APPS/xxx-xxx-res_intermediates/src/com/xxx/xxx/xxx/Manifest.java

    (changed to xxx because these're my client's private data)

    Do you maybe know what can cause it?

    Thank you

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

    You can remove this line,if you have this problem. I can help more if u can share the Android.mk file.

    ReplyDelete
  11. hi,
    how to implement back button in status bar?

    ReplyDelete
  12. hi,
    while building android source code i am getting this error:out/target/common/obj/APPS/SystemUI_intermediates/src/com/android/systemui/R.java:10: duplicate class: com.android.systemui.R

    Even if I execute "rm" command to remove that file or execute "make clean".why is it so?
    I used the same command to build that you mentioned earlier.

    ReplyDelete
  13. I am trying to add a new layout to the framework say my_layout.

    I succeeded partially in that .

    I defined my layout in an xml and put it inside frameworks/base/core/res/res/layout

    I then mentioned the id in frameworks/base/core/res/res/values public.xml and ids.xml

    And after performing a complete build :

    make framework

    make update-api

    make

    i am able to access it as android.R.layout.my_layout

    But now i want to access it through xml also :Eg: "@android:layout/my_layout"

    In which place in the source do i mention this reference ?

    ReplyDelete
    Replies
    1. I resolved it ! I put the files into the sdk also !

      Delete
  14. hey Maniii.

    i write code in my app

    WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
    ViewGroup.LayoutParams.WRAP_CONTENT, 30,
    WindowManager.LayoutParams.TYPE_STATUS_BAR,
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
    PixelFormat.TRANSLUCENT);
    lp.gravity = Gravity.CLIP_HORIZONTAL | Gravity.TOP;

    WindowManager wm = (WindowManager) getApplicationContext()
    .getSystemService(WINDOW_SERVICE);
    wm.addView(view, lp);

    but it's give me an error
    "Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@416607a0 -- permission denied for this window type"

    and also having issue with import "com.android.internal.R."

    can you please help me i want to add one view in status bar but not notification

    thanks in advance :)

    ReplyDelete
  15. Is it passible to do this in an APP on a non-rooted device? Thanks.

    ReplyDelete
  16. In non-rooted device you cannot do this...!!

    ReplyDelete
  17. Awesome.. Thanks for the directory structure explanation of the framework/base/*/com/*/ and it's registration in pathmap.mk

    ReplyDelete
  18. Hi I want to change position of system bar from bottom to top. can you please help on that.

    ReplyDelete
  19. Could you send me that example to my mail id rajeshmcashc10@gmail.com

    ReplyDelete
  20. Could you explain how does the volume buttons are handled by the system? Where do I have to change if I have to change its UI (i.e. UI shown when there is a volume change)

    ReplyDelete
  21. It seems possible to replace status bar on non-rooted devices. I don't know how but there are some apps on the market which add a specific status bar : https://play.google.com/store/apps/details?id=com.tombarrasso.android.wp7barfree

    Any idea ?

    ReplyDelete
    Replies
    1. Im looking for some piece of guide as what you re looking for.
      Not found yet :(

      Delete
  22. hi mani is ur statusbar replaces android default status bar without rooting the device.

    ReplyDelete
  23. hi mani,
    Is their any way to control hide and visible of status bar when we are ohter application like setting screen. Is thier any way to do that. Thanks

    ReplyDelete
  24. Hi mani,

    I tried your method but its not working.it only gives me following error when i try to compile using command make make framework/base/mystatus/res/ .

    Please help...

    find: `out/target/common/docs/gen': No such file or directory
    build/core/java.mk:9: *** frameworks/base/mystatus/res: Target java module does not define any source or resource files. Stop.

    ReplyDelete
  25. Hi Mani,

    I am trying to modify the Statusbar and Navigation Bar height by modifying
    frameworks/base/core/res/res/values/dimens.xml in Android 6.0 AOSP code. I have a rooted device as well. I build and replaced the service.jar , framework.jar and ext.jar in the system/framework folder of my device. But the height is not changing. I replaced the SystemUI.apk as well. Any help is appreciated.



    name="status_bar_height" as 84dp

    name="navigation_bar_height" as 148dp



    Regards,
    Subrat



    ReplyDelete