📜 ⬆️ ⬇️

API secrets of Android devices. Yandex report

One of the main difficulties of Android development is fragmentation. Almost every manufacturer changes Android to fit their needs. Developer Andrei Makeev listed the differences between vendor implementations and the original Android Open Source Project. From the report you can learn how to benefit from the individual features of the firmware on different devices.


- I have been programming from school, I’ve been developing for three years under Android. Of these, I spent a year in Yandex, participated in projects such as Launcher and Telephone.


')
I want to talk about how the fragmentation API Android-devices - both outside, from the application developers, and from the inside, from the point of view of the developers of the platform and phones.

My report consists of two parts. First, let's talk about how the API is fragmented externally. Then we will go through the code - we will find out how the unique feature of the abstract phone is implemented, how the development is built.

API fragmentation is one of the parameters by which a device can be fragmented. The most obvious and simple is the fragmentation of the Android SDK, we face it every day, from the very first days of development for Android. We know what version of the API appeared, what was removed, what was secured, but it is still available and which is no longer there. As a rule, we use different support libraries from Google to simplify our lives. Much has already been done for us.





In our code, it looks something like this: we turn on some functionality, turn off some functionality, depending on what version of the SDK we are in now. If you use, then as a rule, they do the same, but inside.

We will not focus much on this type of fragmentation. The disadvantages are known to all - we have to keep a whole park of devices with different versions in order to at least test our application. In addition, we are forced to write extra code. This is especially inconvenient when you just started developing for Android and suddenly it turns out: you need to learn that it was there two or three years ago in order to support any previous devices. Positive aspects: API is evolving, technologies are moving forward, Android acquires new users, it becomes more convenient for both developers and users.

How do we work with this? We also use libraries and are very happy when we refuse to support older versions. I think in life everyone who has been doing this for more than a year had such a moment. This is straightforward happiness. This is an obvious and simple version of fragmentation. Next - fragmentation by type of Android. There are several of them. Android TV speaks for itself, Android Auto is designed mainly for car stereos, Android Things for IoT and similar embedded devices. W is Wear OS, former Android Watch, watch for Android. We will try to consider how it looks from the developer. And, most interestingly, try to consider how it looks from the inside.



Take two examples from developer.android.com. The first is Wear OS. What do we need to make an application? We add the compileOnly dependency to build.gradle and write two additional lines in the manifest: uses-feature android.hardware.type.watch and uses-library, which corresponds to the same package name as the library we connected.



How do we implement something? We create an Activity, only in this case we will not justify the standard activity that we are used to working with, or even the companion activity, but the WearableActivity, and call its specific methods, in this case setAmbientEnabled (). So, we have a compileOnly-dependency, that is, it does not fall into the course of our application. Uses-library, which apparently forces the OS to connect these classes and this code to us at runtime on the device, and the new classes that we use.



Android Things API is almost the same. We do not prescribe uses-feature, only uses-library, compileOnly-dependency.



We create activity, in this case it is unique for Android Things API, class PeripheralManager. We survey GPIO and try to pledge.



How does such an application behave on your phone? There are two options.



If we have specified that uses-library android: required = ”true”, then we have not fulfilled the mandatory requirements of the PackageManager for installing the application, and it will in principle refuse to install it. If we specify android: required = ”false”, the application will install, but when we try to access the PeripheralManager class, we will get NoClassDefFoundError, because there is no such class in the standard Android.

What are the conclusions from this? We connect the compileOnly dependency only in order to be synced to it during the assembly, and those classes that we use are waiting for us on the device, they are connected with the help of certain lines in the manifest. Sometimes we prescribe a feature that is often needed in order to distinguish on Google Play a device that can or cannot be distributed this application.

I could not single out the negative sides of this kind of fragmentation. Is that those who developed, will tell a lot of stories about how we met completely incomprehensible unfamiliar bugs that we did not encounter on the phone. The positive side, that this additional markets, additional users, additional experience, it is always good.

How to work with it? Write more apps. The general recommendation is to write more than one version of the application for all types of Android, but still do different. For hours, there should be less, for Android Things, virtually nothing of what is written on the phone, does not fit, and so on. And use the libraries that Android developers provide us and, sometimes, device developers.

The next least studied type of fragmentation is fragmentation by manufacturer. Each manufacturer, having received the source code - in rare cases it is an AOSP, more often it is somehow modified by the developers of iron - makes changes to it. As a rule, we learn about the negative effects of this type of fragmentation from not the best channels - from negative reviews on Google Play, because someone has broken something. Or we learn it from crash-analytics, when suddenly something crashes in an incomprehensible way, only on some specific devices. At best, we will learn this from our QA, when something broke when they tested on a specific device.



On this subject, I have a wonderful story from our development Luncher. We received a bug-report, where the activation was not stretched to the full screen, and our favorite default wallpaper was not displayed at all. It was not decoded, an empty window was displayed. We didn't even have the devices to reproduce it. By stretching to the screen, we still managed to find a low-end device on which it did not work, and rather easily corrected everything with the help of android: resizeableActivity = ”true” in the manifest. With the wallpaper everything turned out much more complicated. For about two days we tried to reach out and get more detailed information. In the end, it turned out that a number of devices are either missing or a codec was implemented for decoding progressive jpeg when several compression algorithms were used on each other’s results. In this case, we just wrote a lint-check, which fails the build when building the application, if we put in the apk wallpaper itself, encoded in a progressive way. They recoded all wallpapers, repeated the situation on the backend, which distributes the rest of the wallpaper sets, and everything works fine. But it cost us about two days of proceedings.



It looks like this in code. It is unpleasant, but unfortunately, like this. Usually these lines appear after long debugging.



What guarantees does Google give us to ensure that the API is not broken so that applications do not work in principle? First of all, there is CDD, which describes what can and cannot be changed, what is backward compatible and what is not. This is a document of several dozen pages with general recommendations, which, naturally, will not cover all cases. To cover more cases, there is a CTS that needs to be passed in order for the phone to receive certification from Google and from it you can use Google services. This is a set of approximately 350,000 automated tests. There is also CTS Verifier, the usual APK, which can be put on the phone to conduct a series of checks. By the way, if you buy a phone from your hands, you can check it like this.

With the advent of Treble, a VTS project appeared, this is more likely for developers of lower levels. It checks the driver APIs, which, starting with Project Treble, are versioned and also subjected to similar tests. In addition, the phone developers themselves are sensible people who want Android applications to work normally for them, but this is so-so hope. The downside is that we encounter unforeseen bugs that cannot be predicted until the application has been launched on the device. We are again forced to buy, besides the fact that different versions of the API, also additional devices from different manufacturers to check for them.

But there are positive sides. At least, the features most often implemented by manufacturers fall into Android itself. Someone may remember that the standard Fingerprint API appeared later than devices that could unlock a fingerprint screen. Now, if you believe XDA Developers, unlocking with the camera on the face also wants to make the Android API, but this is not yet accurate. We will most likely find out about this.

In addition, device developers themselves, when they do non-standard APIs, they can, and many publish libraries to work with their APIs for ordinary developers. And if you have never done this, I advise you to go through the statistics of using your application, see which are the most popular manufacturers, and look at the developer portals of their sites. I think you will be pleasantly surprised, many have APIs either with interesting hardware features, with security features, or with cloud services, or with something interesting. And at first glance it seems wild to write separate features for individual devices, but in addition to the devices there are also processor manufacturers, which are even smaller, which also implement their API. For example, Qualcomm has a wonderful hardware acceleration for pattern recognition from the camera, which you can easily use, they even describe it well.

Thus, any of you can get some benefit even from this type of fragmentation. What are we doing about it? Do not hesitate to report bugs and send bug reports to device developers and even Android developers. Because if any APIs were broken for which the CTS test was written, it will be written - and there were such precedents - and after that the API became more reliable.

Learn Android, learn what manufacturers offer, do not argue with them - work with them.

How does it look from the inside? How can you implement some kind of feature that will be unique to our phone, and use this API from a regular Android application?



A bit of theory. How do the Android developers themselves describe the internal structure of the AOSP? The top layer is an application that was written either by you or the developers of the phone itself, which does not have any high rights, simply uses the standard API. This is a framework, these are classes that are not part of your application, such as Activity, Parcelable, Bundle - they are part of the system, they are referred to as a framework. Classes that are available to you on the device. Next are the system services. This is what connects you with the system: ActivityManagerService, WindowManagerService, PackageManagerService, which implement the internal side of interaction with the system.

Next come the Hardware Abstraction Layer, this is the top layer of drivers in which all the logic is laid out for displaying on the screen, for interacting with Bluetooth and all that. The kernel is the bottom layer of drivers, system management. Those who know what a core is and come across, do not need to tell, but you can talk for a long time.



How does it look on the device? Our application not only interacts with the standard framework, but it can also interact with the custom framework of the manufacturer. Also through this framework can communicate with custom services. If these are hardware or low-level features, then HAL is written for them, and even kernel-level drivers, if necessary.



How do we write our feature? The plan is simple: you need to write a framework that is not much different from the libraries that most Android developers wrote, I think you are familiar with this. You need to write a system service, which is a normal application, only with a not quite normal set of rights in the system. And if necessary, you can write HAL, but we omit it. You can write your own drivers at the kernel level, but we also will not consider this now. And write a client application that will use all of this.



In order for us to interact with the system, we need to write some kind of contract, and for this we already have a good mechanism for AIDL interfaces. This is just a kind of interface, on the basis of which the system generates an additional class, which we can extend, through which interprocess communication between your application and system services takes place.



Next, we write the framework, our library, which holds the implementation of this interface, and proxies all calls to it. If you're interested, then ActivityManager, PackageManager, WindowManager work about the same. There is a little more logic than we have implemented here, but the essence is exactly that.



We have implemented the framework, we need to write a system service that will receive our data from the system, in this case we transmit and read the integer. We are creating a class, it is also an extender interface that was generated from AIDL on the previous slide. We create a field in which we write values, we read, we write with a setter, a getter. The only thing is that there are not enough locks, but they didn’t really fit on the slide, they would be worth doing.



Further, in order for this system service to be available, we need to register it with the system service manager, and in this case it is the same class that is not available to ordinary applications. It is available to those that are in the platform in the system sections. We register the service simply in Application.onCreate (), make it available under the name of the class we created.

What do we need for onreate () to start in principle and our service is loaded into memory? We write in the manifest in application android: persistent = ”true”. This means that it is a persistent process, it must be permanently in memory, because it performs system functions.



Also in the manifest we can specify android: sharedUserId, in this case the system, but it can be a wide range of different IDs, they allow the application to get more extensive rights in the system, interact with various APIs and services that are not available to normal applications.

In this case, for example, we did not use anything like that.



We wrote a framework, wrote a system service. We’ll omit the mechanisms inside; this is a somewhat complicated topic; it deserves a separate report.

How to deliver the framework to application developers? Two formats. We can issue full-fledged classes and make a full-fledged library that you integrate into your application, and all the logic will become part of your dexes.

Or you can distribute the framework in the form of stub-classes, for which you can only link at compile-time and expect that these classes will wait for you like previous examples from different versions of Android on the device itself. You can distribute either through the usual Maven repository, which everyone knows, or through Android Studio sdkmanager, in the same way as you install new versions of the SDK. It is difficult to say which method is more convenient. Maven personally is more convenient for me to connect.



We write a simple application. Already in a familiar way, we connect the compileOnly dependency, only now it is our library. We prescribe the uses-library that we wrote and which we put on the device. We write Activity, we get access to these classes, we interact with system. So it would be possible to implement absolutely any features: data transfer to any additional devices, additional hardware features, etc.

Thus, all developers make the unique features of the device available to developers. Sometimes these are private APIs that are given only to partners. Sometimes - public and those that you can find on the developer portals. There are other ways to implement such things, but I described a method that is considered the main one in Google and among Android developers.

Do not treat device developers as people who break your applications. These are the same developers, they write the same code, at about the same level. Write bug reports, they really help, I often analyze them. Write more apps and take advantage of the opportunities that both Android and the device itself provide you. That's all I wanted to say.

Source: https://habr.com/ru/post/448126/


All Articles