glyph-api icon indicating copy to clipboard operation
glyph-api copied to clipboard

[WIP] Initial android project to spoof com.ubercab for hacky LED control

Open Raghav-B opened this issue 2 years ago • 11 comments

Just got a Nothing Phone 2 and was severely disappointed to find no SDK/API available for devs to customize the LEDs. I came across this repository and decided to pitch in a little bit.

Overview

Basically, I've found out we can create a spoof app with the package name com.ubercab and can trigger "Uber" notifications via this to somewhat programmatically control the Glyph interface through the built-in Glyph Progress app. I've written a very early proof of concept for now which incrementally moves the "timer" LED Glyph bar every time a button is pressed.

Within the limited scope of using the interface made available to the Uber app by Nothing, I believe there are still additional animations we can enable beyond the timer. Will continue investigating this in this PR.

Of course, a side effect of my current approach is that a user cannot have the official Uber app installed on their device LOL.

Findings

1) Decompiling com.nothing.glyphnotification

Inspired by your decompilation of the Composer app, I tried to explore another avenue with the built-in Glyph Progress app.

Naturally, this could allow us to explore the app/service which controls the Glyph timer and other such apps. We may find more hacky interfaces to control the LEDs via that.

After decompiling the Glyph Progress app, I quickly came across the same bindService() calls and the GlyphInterface interface with its setFrameColors(), register(), etc. methods.

Interestingly, I did find an API key for Glyph Progress app hardcoded inside the AppManifest.xml. I tried to use this API key with my own app with the register() call. However, as noted by you, this failed because:

  1. I can't update/uninstall the built-in com.nothing.glyphnotification package with my own package
  2. Even if I could update the built-in app, I don't have Nothing's signing key so the signature check would fail

2) How Glyph Progress (com.nothing.glyphnotification) animates on custom notifs

Seeing as I couldn't directly control the GlyphInterface, my next bet was to figure out how Uber, Zomato and so on are integrating the LEDs with their respective apps. It seems com.nothing.glyphnotification listens to all notifications on your device via a service, and filters to a select few package names. Here are the apps whose notifications it can potentially respond to:

  • com.ubercab
  • com.application.zomato
  • com.chang.andy.notifystyle
  • com.chang.andy.notifygen
  • com.test.zomato

I say potentially because for now Nothing has disabled the last 4 and has limited their functionality to dev OS builds. They perform this check via a isMPBuild() method (I assume this stands for mass-produced build) which performs the following check:

return "mp".equals(SystemProperties.get(BUILD_TYPE_KEY));
// where BUILD_TYPE_KEY = "ro.build.version.type"

In any case, how all of this works is that third-party developers are expected to provide their own implementations of a BasePackageNotificationController, and a BaseNotificationParser which contains a bunch of BaseNotificationParserRule objects. These implementations are included in the com.nothing.glyphnotification source code, which upon initialization builds a map within a module called GlyphNotificationManager which points package names to their respective BasePackageNotificationController implementations.

Here is the map being built in the GlyphNotificationManager.initControllers() method

private final void initControllers() {
    putController(new UberController());
    putController(new ZomatoController());
    putController(new NotifyStyleController());
    putController(new NotifyGenController());
    putController(new NotifyTestZomatoController());
    updateDemoEnable();
}

The updateDemoEnable() methods is what switches off functionality for the other 4 controllers based on the user's OS build.

Anyways, this map that's being built is used by the glyph notification service to respond to only those android StatusBarNotifications whose package names and corresponding controllers are inside the map.

Here's an example of Uber's BasePackageNotificationController implementation which specified the package name to be checked in the notification service callback.

public final class UberController extends BasePackageNotificationController {
    public UberController() {
        super("com.ubercab");
    }

    public void init() {
        getParsers().add(new UberParser(getPackageName()));
    }
}

Nothing special. Most of the work happens instead happens inside the implementations of BaseNotificationParser. In Uber's case, this is called UberParser. This responds to 6 different types of notifications (listed at the bottom) and controls various animations based on data included inside each StatusBarNotification object that com.nothing.glyphnotification's service listens to. Sorry, before being used by the parser, the built-in Android StatusBarNotification object is first used to initialize a GlyphNotification object.

This is where the BaseNotificationParserRules come into play, as they act on details inside GlyphNotification. Here's an example of the one I'm currently using to programmatically control a timer animation:

public static final class UberPickupTimeParserRule extends BaseNotificationParserRule {

    public int updateGlyphResult(GlyphNotification glyphNotification, Context context) {
        String value;

        String title = glyphNotification.getTitle();
        if (title != null) {
            if (isMatchRegexString(context, "bid_driver_offer_pickup_minute", title)
                    || isMatchRegexString(context, "bid_driver_offer_pickup_minutes", title)) {

                Integer num = null;
                MatchResult find$default = Regex.find$default(new Regex("\\d+"), title, 0, 2, (Object) null);
                if (!(find$default == null || (value = find$default.getValue()) == null)) {
                    num = StringsKt.toIntOrNull(value);
                }
                if (num != null) {
                    getGlyphResult().addChannel(Glyph.f191C2);
                    getGlyphResult().setProgress(calculateProcess(num.intValue()));
                    getGlyphResult().setActualProgress(calculateActualProcess(num.intValue()));
                    getGlyphResult().setValid(true);
                    getGlyphResult().setTime(SystemClock.elapsedRealtime());
                    return 1;
                }
            }
        }
        return 0;
    }
}

Following from this, each rule seems to populate a GlyphResult object based on the notification details available in the GlyphNotification object. We can control the notifications we are sending to trigger different rules, and in turn change the GlyphResult object. This GlyphResult object is then finally used by the GlyphInterface that you previously discovered to animate stuff.

3) Some interesting member variables in GlyphResult

Right now the BaseNotificationParserRules in each BaseNotificationParser implementation don't use all features of the GlyphResult shown below. I imagine if Nothing eventually releases an SDK to devs, it would probably interface with this GlyphResult class to create custom LED effects.

public final class GlyphResult {
    private int actualProgress = -1;
    private boolean animation;
    private boolean breathing;
    private boolean colorType;
    private int[] colors;
    private int interval = 500;
    private final HashSet<Integer> lightChannel = new HashSet<>();
    private int period = 4000;
    private int progress = -1;
    private long time;
}

Future work

I'll contiue seeing what other animations I can trigger with this current approach. Seems promising right now as there's at least some way of programmatically telling the Glyph interface to do what we want, limited as it may be to Uber's implementation. However, the current approach comes with the following caveats:

  • A fake "Uber" notification is continually present for the duration of the glyph animation. Maybe we can use some Android API to hide this notification as soon as its triggered - not sure, I'm not too familiar with Android development.
  • A user can't have the official Uber app installed while they use the spoof app.

Future animations I want to try with this approach:

  • Can I address individual LEDs and make them flash? Or am I limited to a flashing anim defined by Uber?
  • Can I have the progress bar go back down? Could be cool as a basic music visualizer

For reference, here are the class names for the other parser rules by Uber. If anyone has actually used Uber with the Nothing Phone 2, I guess you can advise us on what sort of animation each rule triggers.

  • UberPickupTimeParserRule (What I'm using rn)
  • UberDriverArrivingParserRule
  • UberDriverArrivedParserRule
  • UberBoardingParserRule
  • UberCancelParserRule
  • UberPairingDriverParserRule

Raghav-B avatar Aug 21 '23 21:08 Raghav-B

That's awesome, great work!

I've just added you as a collaborator so you can merge whenever you feel it's ready and push any future updates you might have. Would be great if you could add the description of this PR to your Notes.md or something so we have them in the repo :)

Cheers!

rec0de avatar Aug 22 '23 09:08 rec0de

Thank for adding me! Will update Notes.md and add a link to it in the README before merging this in.

As a bit of a small update: it seems right now we're limited to some very fixed animations with the Uber spoofing approach. Overall right now UberParser and its implemented BaseNotificationParserRules are only able to control the ring of LEDs around the NFC/wireless charging area of the phone.

Here's some more specifics on the animations that currently work (with some limitations):

  • Progress indicator that can only increase and not decrease. Decreasing the progress is limited by the BaseNotificationPackageController class implementation which ignores any "negative" progress. I'll explore this a bit more.
    • Uses UberPickupTimeParserRule
  • Looping breathing animation in the top left LED
    • Uses UberPairingDriverParserRule
  • A flashing anim which cycles around the NFC every 5 or so seconds
    • Uses UberDriverArrivedParserRule

I managed to keep the fake notifications we push out hidden in the status bar through a hacky approach of performing a 200ms delayed notificationManager.cancel() call on notifications we push out. Also added a button which allows cancelling any running animations (previously I had to restart my phone to stop them running lol)

One piece of good news though is that these animations continue running despite the phone being locked, so I think it can enable the following use cases:

  • Allowing for multiple tiers of Essential notifications. We can have the existing ones in the system that light up the diagonal LED, and can configure the spoof app to play alternate animations for other package's notifications.
  • Some integration with Spotify/other music players to show the current song progress.
  • A nice "screensaver" with the breathing animation

Raghav-B avatar Aug 22 '23 10:08 Raghav-B

By the way, I wanted to ask about your process for decompiling Composer to see the implementation of GlyphService.java that you showed in the README. Did you have to decompile another system app for this?

When I use JADX to decompile Composer I can only really see GlyphInterface.

Raghav-B avatar Aug 22 '23 10:08 Raghav-B

Also added a button which allows cancelling any running animations (previously I had to restart my phone to stop them running lol)

I also noticed that, but you can also turn the glyph lights off and on again in the settings to avoid restarting the phone entirely :)

Decreasing the progress is limited by the BaseNotificationPackageController class implementation which ignores any "negative" progress.

I think you might be able to "fake" that by sending a cancel immediately followed by the decreased progress?

One piece of good news though is that these animations continue running despite the phone being locked, so I think it can enable the following use cases:

* Allowing for multiple tiers of Essential notifications. We can have the existing ones in the system that light up the diagonal LED, and can configure the spoof app to play alternate animations for other package's notifications.

* Some integration with Spotify/other music players to show the current song progress.

* A nice "screensaver" with the breathing animation

Pretty cool, that's a decent amount of use-cases right there!

rec0de avatar Aug 22 '23 11:08 rec0de

By the way, I wanted to ask about your process for decompiling Composer to see the implementation of GlyphService.java that you showed in the README. Did you have to decompile another system app for this?

The GlyphService, and most of the other snippets shown in the readme, are from the com.nothing.thirdparty system app, which provides the glyph API the composer uses.

rec0de avatar Aug 22 '23 11:08 rec0de

Hey @Raghav-B & @rec0de can I find you on Discord by any chance? We're looking for people to collaborate and experiment with Glyph.

TheRealMrSteel avatar Nov 24 '23 15:11 TheRealMrSteel

Hi @TheRealMrSteel - I don't really have the time to work on glyph currently, but you can always email me if you have any specific questions or want to drop your discord handle. [email protected]

rec0de avatar Nov 24 '23 18:11 rec0de

Hey @TheRealMrSteel, you can find me on Discord @ ephemeralrag. Happy to hop on any discussions

Raghav-B avatar Nov 27 '23 02:11 Raghav-B

Hi @rec0de thanks for you response, appreciate it! Good to know that you're currently limited in time. Will reach out to Raghav-B first.

Hi @Raghav-B I sent you a friend request on Discord (seems like you're not yet part of the Nothing Server) So I can't directly DM you yet.

TheRealMrSteel avatar Nov 27 '23 10:11 TheRealMrSteel

https://nothing.community/d/5595-glyph-developer-kit-its-here

TheRealMrSteel avatar Feb 22 '24 09:02 TheRealMrSteel

oh hey, awesome! thanks for the heads up, i've added an update to the readme with links to the official documentation

rec0de avatar Feb 22 '24 11:02 rec0de