Fleet management platformAn enterprise IoT fleet management platform for transportation and logistics See Case Study
Fleet management platformAn enterprise IoT fleet management platform for transportation and logistics See Case Study
Home Blog Introducing New Service for Android 7 | Navigation Bar Customization Back to blog 11 minutesIntroducing New Service for Android 7 | Navigation Bar Customization IntroLet’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: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 formulationWe 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: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. ImplementationDownload and setup AOSPDownload 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 serviceGoing forward here is detailed map of components affected by our implementation: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:Show code package android.os; /** * /framework/base/core/java/android/os/NavBarExServiceMgr.java * It will be available in framework through import android.os.NavBarExServiceMgr; */ import android.content.Context; import android.widget.RemoteViews; public class NavBarExServiceMgr { private static final String TAG = "NavBarExServiceMgr"; private final Context context; private final INavBarExService navBarExService; public static NavBarExServiceMgr getInstance(Context context) { return (NavBarExServiceMgr) context.getSystemService(Context.NAVBAREX_SERVICE); } /** * Creates a new instance. * * @param context The current context in which to operate. * @param service The backing system service. * @hide */ public NavBarExServiceMgr(Context context, INavBarExService service) { this.context = context; if (service == null) throw new IllegalArgumentException("service is null"); this.navBarExService = service; } /** * Sets the UI component * * @param ui - ui component * @throws RemoteException * @hide */ public void setUI(INavBarExServiceUI ui) throws RemoteException { navBarExService.setUI(ui); } public String addView(int priority, RemoteViews remoteViews) { try { return navBarExService.addView(priority, remoteViews); } catch (RemoteException ignored) {} return null; } public boolean removeView(String id) { try { return navBarExService.removeView(id); } catch (RemoteException ignored) {} return false; } public boolean replaceView(String id, RemoteViews remoteViews) { try { return navBarExService.replaceView(id, remoteViews); } catch (RemoteException e) {} return false; } public boolean viewExist(String id) { try { return navBarExService.viewExist(id); } catch (RemoteException e) {} return false; } } This class should be registered in the SystemServiceRegistry static section:Show code registerService(Context.NAVBAREX_SERVICE, NavBarExServiceMgr.class, new CachedServiceFetcher() { @Override public NavBarExServiceMgr createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(Context.NAVBAREX_SERVICE); INavBarExService service = INavBarExService.Stub.asInterface(b); if (service == null) { Log.wtf(TAG, "Failed to get INavBarExService service."); return null; } return new NavBarExServiceMgr(ctx, service); }}); From the client side the service can be accessed in this way:Show code NavBarExServiceMgr navBarExServiceMgr = (NavBarExServiceMgr) getSystemService(Context.NAVBAREX_SERVICE); // TODO 2. Define service AIDL interface:Show code /* * aidl file : * frameworks/base/core/java/android/os/INavBarExService.aidl * This file contains definitions of functions which are * exposed by service. */ package android.os; import android.os.INavBarExServiceUI; import android.widget.RemoteViews; /** */ interface INavBarExService { /** * @hide */ void setUI(INavBarExServiceUI ui); String addView(in int priority, in RemoteViews remoteViews); boolean removeView(in String id); boolean replaceView(in String id, in RemoteViews remoteViews); boolean viewExist(in String id); } And implement it:Show code public class NavBarExService extends INavBarExService.Stub … 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:Show code @Override public void setUI(INavBarExServiceUI ui) { Log.d(TAG, "setUI"); this.ui = ui; if (ui != null) { try { for (Pair entry : remoteViewsList.getList()) { ui.navBarExAddViewAtEnd(entry.first, entry.second); } } catch (Exception e) { Log.e(TAG, "Failed to configure UI", e); } } } addView method implementation:Show code @Override public String addView(int priority, RemoteViews remoteViews) throws RemoteException { String id = UUID.randomUUID().toString(); int pos = remoteViewsList.add(priority, id, remoteViews); if (ui != null) { if (pos == 0) ui.navBarExAddViewAtStart(id, remoteViews); else if (pos == remoteViewsList.size() - 1) ui.navBarExAddViewAtEnd(id, remoteViews); else { // find previous element ID Pair prevElPair = remoteViewsList.getAt(pos - 1); ui.navBarExAddViewAfter(prevElPair.first, id, remoteViews); } } return id; } 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:Show code try { traceBeginAndSlog("StartNavBarExService"); ServiceManager.addService(Context.NAVBAREX_SERVICE, new NavBarExService(context)); Slog.i(TAG, "NavBarExService Started"); } catch (Throwable e) { reportWtf("Failure starting NavBarExService Service", e); } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); Adding new service requires Android SEPolicy adjusting.Add new entry to {aosp}/system/selinux/service.te:Show code type navbarex_service, app_api_service, system_server_service, service_manager_type; And new entry to {aosp}/system/selinux/service_contextsShow code navbarex u:object_r:navbarex_service:s0 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:Show code /** * Use with {@link #getSystemService} to retrieve a * {@link android.os.NavBarExServiceMgr} for using NavBarExService * * @see #getSystemService */ public static final String NAVBAREX_SERVICE = "navbarex"; 5. Define INavBarExServiceUI interface:Show code /* * aidl file : * frameworks/base/core/java/android/os/INavBarExServiceUI.aidl * This file contains definitions of functions which are provided by UI. */ package android.os; import android.widget.RemoteViews; /** @hide */ oneway interface INavBarExServiceUI { void navBarExAddViewAtStart(in String id, in RemoteViews remoteViews); void navBarExAddViewAtEnd(in String id, in RemoteViews remoteViews); void navBarExAddViewBefore(in String targetId, in String id, in RemoteViews remoteViews); void navBarExAddViewAfter(in String targetId, in String id, in RemoteViews remoteViews); void navBarExRemoveView(in String id); void navBarExReplaceView(in String id, in RemoteViews remoteViews); } 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:Show code public class VendorServices extends SystemUI { private final Handler handler = new Handler(); private NavBarExServiceMgr navBarExServiceMgr; private volatile PhoneStatusBar statusBar; private INavBarExServiceUI.Stub navBarExServiceUI = new INavBarExServiceUI.Stub() { @Override public void navBarExAddViewAtStart(final String id, final RemoteViews remoteViews) { if (!initStatusBar()) return; handler.post(new Runnable() { @Override public void run() { statusBar.navBarExAddViewAtStart(id, remoteViews); } }); } //… } @Override protected void onBootCompleted() { super.onBootCompleted(); navBarExServiceMgr = (NavBarExServiceMgr) mContext.getSystemService(Context.NAVBAREX_SERVICE); if (navBarExServiceMgr == null) { Log.e(TAG, "navBarExServiceMgr=null"); return; } try { navBarExServiceMgr.setUI(navBarExServiceUI); } catch (Exception e) { Log.e(TAG, "setUI exception: " + e); } } } 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:Show code private boolean initStatusBar() { if (statusBar == null) { synchronized (initLock) { if (statusBar == null) { statusBar = getComponent(PhoneStatusBar.class); if (statusBar == null) { Log.e(TAG, "statusBar = null"); return false; } Log.d(TAG, "statusBar initialized"); } } } return true; } 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:Show code public void navBarExAddViewAtStart(String id, RemoteViews remoteViews) { if ((mRot0 == null) || (mRot90 == null)) return; ViewGroup ends0 = (ViewGroup) mRot0.findViewById(R.id.ends_group); ViewGroup ends90 = (ViewGroup) mRot90.findViewById(R.id.ends_group); if ((ends0 == null) || (ends90 == null)) return; navBarExAddView(0, id, remoteViews, ends0); navBarExAddView(0, id, remoteViews, ends90); } private void navBarExAddView(int index, String id, RemoteViews remoteViews, ViewGroup parent) { View view = remoteViews.apply(mContext, parent); view.setTag(navBarExFormatTag(id)); TransitionManager.beginDelayedTransition(parent); parent.addView(view, index); } 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:Show code LOCAL_SRC_FILES += \ core/java/android/os/INavBarExService.aidl \ core/java/android/os/INavBarExServiceUI.aidl \ ... And only INavBarExService.aidl in “aidl_files” section:Show code aidl_files := \ frameworks/base/core/java/android/os/INavBarExService.aidl \ ... All togetherComplete 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:Download stock Android source codes and setup build environment;Apply patches;Build custom Android SDK;Build custom ROM and flash the device;Run demo application. Android SDK can be build using the following commands:Show code . build/envsetup.sh lunch sdk_x86-eng make sdk -j16 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):Show code . build/envsetup.sh lunch aosp_flounder-userdebug make otapackage -j16 And flashed to the device usingShow code fastboot -w flashall 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.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).DrawbacksAfter 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. SummaryWe 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: No Shake: Screen Stabilization in AndroidAndroid: Draw a Custom ViewHow-to Guide for OBDII Reader App Development Mobile Development Digital Experiences Article Contents: