Introducing New Service for Android 7 | Navigation Bar Customization

09 Jun 2017
Introducing New Service for Android 7 | Navigation Bar Customization

Intro

Let’s imagine the following situation: we are developing a product that requires very specific features that do not exist or is not available in Android’s inventory. For example, it requires a credit card reader. Yes, I know about external readers, but we are serious developers and decided to make it internal so that the final device will look more consistent. Like this one for example:

Introducing New Service for Android 7 | Navigation Bar Customization

Such devices most likely should provide extra services that can be utilized to process payments for 3-rd party developers.

Hence, in this article I would like to elaborate upon how to extend Android API by using the example of introducing a new service for customizing device’s navigation bar. 

Task formulation

We are going to provide an API to allow customization of Android’s navigation bar by adding new items there. Standard Android navigation bar varies in appearance throughout different devices, however the devices with hardware buttons don’t have a navigation bar.

Let’s take a Nexus 9 device:

Introducing New Service for Android 7 | Navigation Bar Customization

Navigation bar is black rectangle on the bottom with navigation buttons. It is visible to the user all the time (except when applications run in  full-screen mode) so it would be good to provide a way to place some custom things there.

In order to make it flexible as much as possible, let’s use Android Remote Views as our API parameter. This will allow the user (i.e. another developer) to use many standard views for customization of the navigation bar. Also this will provide a callbacks mechanism from custom views to user applications.

As extensions point out, let’s use Android SDK directly: we will customize AOSP and build our own Android SDK. In reality, such SDK is only valid for devices that run our custom ROM but by default that is ok for us.. As a result, our services will be very easy to use and will be available as an out of the box solution within Android SDK. 

Implementation

Download and setup AOSP

Download and setup AOSP build environment. All necessary information is available here. Select correct AOSP branch (Android version) compatible with selected device. In my case it is Nexus 9 and branch android-7.1.1_r33. 

Implement new service

Going forward here is detailed map of components affected by our implementation:

Introducing New Service for Android 7 | Navigation Bar Customization

The general idea here is to expose the navigation bar extended functionality implemented on UI side to the client applications through Android service. The Navigation bar is defined in NavigationBarInflaterView.java and {aosp}/frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml file. But we can’t access this stuff directly from client app because it is located in SystemUI application while core Android services is located in system_process. 

So our goal here, is to link these components with system_process - where they will be available for client code.

1. Create service proxy (manager) that will be visible from client applications. It is a standard Java class which delegates all of the functionality to the service below:

This class should be registered in the SystemServiceRegistry static section:

From the client side the service can be accessed in this way:

2. Define service AIDL interface:

And implement it:

As you see most of the methods are pretty straightforward. setUI looks different. It is an internal method used by the class that implements INavBarExServiceUI interface to register itself in NavBarExService. All the things marked with “@hide” comment will be cut from the final Android SDK and thus will be not visible from the client side.

Some notes about API semantics:

  • Every notification entry is identified by string identifier. It is generated internally when adding new view. So any application that adds any new entry should persist returned ID in order to perform any subsequent calls;
  • Other methods takes ID as a parameter that identifies notification entry;
  • Every entry has also priority parameter which specifies where to place the entry. The higher the value is the left position will be.

setUI method implementation:

addView method implementation:

remoteViewsList is used to hold all created entries until UI is not attached (setUI method is called). In case it is already attached (ui field is not null) new entry is added to the UI immediately.

3. SystemServer should register our service in the system:

Adding new service requires Android SEPolicy adjusting.

Add new entry to {aosp}/system/selinux/service.te:

And new entry to {aosp}/system/selinux/service_contexts

Note that SELinux files and its format for Android 7.0 are different comparing with previous Android versions.

4. Add service name constant to the Context class so clients can use it instead of hardcoded string:

5. Define INavBarExServiceUI interface:

Note it is marked with “hide” attribute, making it invisible for the clients. Also note it is oneway. This will make IPC from system_process to SystemUI faster because all calls will be non blocking (but this requires void return value).

 6. VendorServices is the standard SystemUI component (it extends SystemUI class) that implements INavBarExServiceUI interface:

onBootCompleted method is interesting. It performs self registration (calling setUI method) in NavBarExService through NavBarExServiceMgr. Also take into account that SystemUI process can be restarted (for example duo to crash) so several calls to setUI method will be made. Of course only the last one should be taken into consideration.

7. PhoneStatusBar class is key link between NavBarExService and NavigationBarInflaterView. It holds a reference to NavigationBarView which in turns holds reference to NavigationBarInflaterView. From the other side VendorServices retrieves PhoneStatusBar reference using SystemUI.getComponent method:

Did you notice such fancy “if (statusBar == null)” constructions? It is called “Double Checking Locking Pattern”. The following goal is pursued : perform thread-safe object initialization, but avoid entering synchronization section when the object is already initialized. PhoneStatusBar and NavigationBarView changes are pretty simple: they just delegate all calls to NavigationBarInflaterView class.

8. NavigationBarInflaterView class is the final class that can perform actual UI changes. Here is navBarExAddViewAtStart method:

This code rely on existing implementation and uses mRot0, mRot90 and R.id.ends_group as a ViewGroup for custom views. mRot0, mRot90 represents layouts for portrait and landscape orientations so we are adding custom view to both of them. Also we engaged TransitionManager to perform some default animation.

9. One note about Android.mk file. It should contain our AIDL files references. Two files in LOCAL_SRC_FILES section:

And only INavBarExService.aidl in “aidl_files” section:

All together

Complete source codes are available on GitHub. There are two files in “patches” directory: frameworks_base.patch and system_sepolicy.patch. The first one should be applied to “{aosp}/frameworks/base” directory. The second one - to “{aosp}/system/sepolicy” directory.

NavBarExDemo directory contains demo application in gradle style build system.

To check the implementation on the real device we need:

  1. Download stock Android source codes and setup build environment;
  2. Apply patches;
  3. Build custom Android SDK;
  4. Build custom ROM and flash the device;
  5. Run demo application.

 Android SDK can be build using the following commands:

The resulting ZIP file is located in {aosp}/out/host/linux-x86/sdk/sdk_x86 directory (in case if launched on Linux). Note that the final archive contains your Linux user name in file name. To change this behavior define “BUILD_NUMBER” environment variable and its values will be used instead of the user name. The J parameter refers to how many simultaneous tasks are allowed. Use the doubled value of your processor cores. 

Firmware can be created using the following commands (in case of Nexus 9 device):

And flashed to the device using

command. The device should have an unlocked bootloader for this. 

To check results special demo Android application was created. It allows to manage custom navigation bar entries and handle its clicks.

Introducing New Service for Android 7 | Navigation Bar Customization

Clicking on a custom items will show toast even when application is not started (i.e. to process broadcast message application will be started by the system if needed).

Introducing New Service for Android 7 | Navigation Bar Customization

Drawbacks

After a reboot, the device will lost all of its customization. So it would be good to save the customization somewhere. It would also be good to add a gravity parameter into the API, to control item’s position more precisely. 

Summary

We extended Android SDK and introduced new core service intended for customizing Android navigation bar. Also we created custom firmware for Nexus 9 tablet that implements this service.

In the previous article we implemented screen stabilization for Nexus 7. Here is the same implementation but for Nexus 9:

Hope you have found this article useful, don't forget to leave your comments below. If you have a project idea in mind, but don't know where to start, we're always here to help you

SEE ALSO: