You can fix the 0.1%

Antonin Fouques
The Qonto Way
Published in
6 min readJul 3, 2019

At Qonto we care for every single customer and don’t hesitate to fix uncanny bugs for your experience and the community.

TL;DR:

  • Google added the TextInputLayout component in its Support Library
  • Meizu modified the AOSP and in particular the Editor class
  • Further evolutions of the TextInputLayout component introduced a crash on Meizu devices only
  • We understood the root cause of those crashes and decided to update the Android Support Library to fix it

Part 1 — Meizu and Material crash’s resolution

In november 2018, the Android team released their library Material Component. Since then, all applications containing the TextInputEditText running on Meizu devices experienced crashes.

Workaround

This crash led to a lot of noise on the issue tracker of Android applications and libraries, on StackOverflow, and eventually on the Android issue tracker.

A workaround was quickly found: instead of using TextInputEditText inside a TextInputLayout, prefer an AppCompatEditText (which supports compatible features on older versions of the platform) or an EditText, inside a TextInputLayout.

It does work by getting rid of the TextInputEditText modification (see part 2. for more details), which fixed accessibility issues. But by using this workaround, developers create unusable interface for visually impaired users.

Investigation

The main issue comes from Meizu modifying the AOSP, but as Meizu doesn’t update its devices and because its modifications are not Open Source, this path looks like a dead-end.

On the other hand, the Material Component library (containing the TextInputLayout) is Open Source, available on Github and is updated every month. Moreover, the Android team had a look at this Meizu issue and found the commit that led to the crash. But since they do not own any Meizu device, they encouraged the community to help out and propose a fix through a merge request.

As our company, Qonto, bought us a Meizu device to test the above workaround, we decided to understand and fix the issue.

First of all we had to understand 3 things:

The purpose of the Android team commit

Reading the commit on Github might be enough to understand its purpose, thanks to the commit description, the comments in the code and the code itself.

The purpose of the Meizu framework modification, and the conflict leading to the crash

To investigate, we cloned the Material Component repository on our workstation and followed the repo documentation to know How to build and How to contribute. Eventually, it’s even simpler to only open the whole project in Android Studio and test the included Catalog showcase app. This way we can modify the library then test the code directly in the Catalog app.

First, we ran the Catalog app on the Meizu device and confirmed the crash. We then did the same steps with an emulator or another device and confirmed there is no crash.

Same EditText crash on Meizu and not on Emulator

Now that we did confirm the bug, we put some debug break points in the library code following what we learned from the crash stacktrace and from the commit leading to the crash. We added a breakpoint in the TextInputEditText.getHint() method to see the following executed code:

Same code run on Meizu device and on AOSP Emulator

From that debug trace we were able to reverse engineer that part of the code:

void updateCursorsPositionsMz(...) {
[...]
if (!TextUtils.isEmpty(mTextView.getHint())
&& TextUtils.isEmpty(mTextView.getText()) {
// getHint() overriden by TextInputEditText
// return the TextInputLayout hint so the test is true

Layout hintLayout = mTextView.getHintLayout();
// getHintLayout() is a final TextView class.
// As TextView.mHint is null, it returns null, but the
// developer assumed it will always return a non-null value

Int value = hintLayout.getLineForOffset(offset);
// As hintLayout is null, this leads to a NullPointerException

[...]
}
}

We discovered the Meizu sources while writing this post, which confirmed our guesses. The purpose of the Meizu modification might be that they want to draw the cursor slighly differently when there is a hint and no text, but we didn’t observe this kind of behaviour in the Meizu apps.

Fixing the problem

After this investigation, we shared our results on the related Google issue thread.

The first idea to fix the issue was to ignore the TextInputEditText.getHint() modification if the device OEM running the code is Meizu. As we had to add an OEM’s exception in a generic code, we were not confident with this solution. However it was the best we found, and it worked pretty well if we look at the piece of code above:

  • in the Meizu updateCursorsPositionsMz() method, the mTextView.getHint() is now done on the TextInputEditText’s parent, the TextView.
  • The returned value is now null and not the TextInputLayout.mHint
  • The crashing if condition is not run anymore.

We submitted this modification as a commit in a Pull Request in the Android Material Component repository. After a quick signature on the Google Contributor License Agreement, the review started with Cameron Ketcham as reviewer and maintainer of the library.

Cameron asked me to do more tests and to try other fixes. After some more investigations and reviews from his team, we finally agreed on another kind of solution: if the code runs on a Meizu device and if the TextView doesn’t provides a hint, we will set it an empty one at the View creation:

@Override
protected void onAttachedToWindow() {
// Meizu devices expect a hintLayout if the hint is not null. In order to avoid crashing,
// we force the creation of the hintLayout by setting an empty non null hint.
TextInputLayout layout = getTextInputLayout();
if (layout != null
&& layout.isProvidingHint()
&& super.getHint() == null
&& Build.MANUFACTURER.equals("Meizu")) {
setHint("");
}
super.onAttachedToWindow();
}

With this solution, on a Meizu device, the TextInputEditText.getHint() still returns the TextInputLayout’s hint so the accessibility update of the library still works as expected. Moreover, as TextView.setHint("") is called, a TextView.mHintLayout is created. Then, in the Meizu updateCursorsPositionsMz() method, the crashingif condition can now call mTextView.getHintLayout().getLineForOffset(offset) without NullPointerException.

This last commit was accepted by the Android team, the Google issue was closed, the code was merged in the master branch and released in the 1.1.0-alpha07 version of the library.

Results and Conclusion

Even if it is a fix for only 0.01% to 1% of the market, it has been really appreciated by the Android developers community.

What made this fix interesting is that it only took one particular phone and a few hours of cumulative work to understand and find the best fix possible.

Moreover, the collaboration with the Android Team was excellent and really reactive even if it was our first contribution to the Google source code.

As a last word, fixing the 0.1% is essential as it is not always difficult, it shows you care about all your users, and it can help not only your users but also all the other applications’ users.

Sorry for the picture ;) …

Continue on Part 2 — How AOSP creates incompatibilities.

Thank you to the Qonto Android team (Guillaume Le Roy, BoD and Jean-Philippe Denis) for the help and reviewing this article.

--

--