Nothing Phoneの背面についてるライトをいじれるSDKが公開されてたので試す【Glyph Interface】Glyph Developer Kit

NothingPhoneの背面に無駄に搭載されているライト「Glyph Interface」をいじれるSDKが公開されていることを今日知りました

https://jp.nothing.tech/pages/glyph-developer-kit

https://github.com/Nothing-Developer-Programme/Glyph-Developer-Kit

Phone(2a)をメインで使用しているので試してみます

## 今回作ったもの

https://github.com/SousiOmine/GlyphInterfaceExample

## 環境
- Windows11 64bit 23H2
- Android Studio Koala Feature Drop 2024.1.2
- Nothing Phone(2a) 国内版
- Nothing OS 2.6

## セットアップ
プロジェクトはEmptyViewsActivityテンプレートから言語をJavaに、MinimumSDKはNothingPhone以外サポートする必要がないので、API31(Android12)にして作成しました
（Javaなのは私がkotlin未履修のためです。kotlinできる方は適宜読み替えてください）
ちなみにSDKのGitHubリポジトリのReadmeにあるサンプルコードもJavaになっていました

NothingPhoneの開発者オプションからデバッグを有効化しようとすると「Glyph Interfaceのデバッグモード」という項目があるのを発見したので有効にします
![Screenshot_20240905-092944.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/7174a232-9bde-9e55-b94e-de6dc1b1b787.png)

GlyphいじれるSDKをアプリで利用するには公式Webページから申請できるAPIキーが必要なのですが、このオプションを有効後48時間はキーを取得せずともテストキーで開発できるようです

## 試す
GlyphいじいじSDKはどこかのリポジトリに上がっているわけではなくjarファイルで提供されているのでプロジェクトに追加します
↓参考

https://github.com/Nothing-Developer-Programme/Glyph-Developer-Kit/issues/2

GlyphいじいじSDKの利用には権限とAPIキーの記述が必要なので、AndroidManifest.xmlに書きます
```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application>
        省略
        <!-- 権限 -->
        <uses-permission android:name="com.nothing.ketchum.permission.ENABLE"/>
    </application>

    <!-- APIキー書くところ -->
    <meta-data android:name="NothingKey" android:value="test"/>
</manifest>
```
ちなみに書き忘れると怖いエラーが出ます（一敗）
```Error:Error
java.lang.SecurityException: Not allowed to bind to service Intent { act=com.nothing.thirdparty.bind_glyphservice pkg=com.nothing.thirdparty cmp=com.nothing.thirdparty/.GlyphService }
```

Glyphインターフェースの操作はGlyphManagerクラスのインスタンスからいじいじするようなので、まずGlyphManagerクラスのインスタンスを作成→初期化、終了時には解放しないといけないようなのでアプリ終了時に開放する処理をreadmeをパクりながら書いてみました

```java:MainActivity.java
package com.example.firstglyph;

import android.content.ComponentName;
import android.os.Bundle;
import android.util.Log;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.nothing.ketchum.Common;
import com.nothing.ketchum.GlyphException;
import com.nothing.ketchum.GlyphFrame;
import com.nothing.ketchum.GlyphManager;

public class MainActivity extends AppCompatActivity {

    GlyphManager gManager = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        GlyphManager.Callback gcallback = new GlyphManager.Callback() {
            @Override
            public void onServiceConnected(ComponentName componentName) {
                if (Common.is20111()) gManager.register(Common.DEVICE_20111);
                if (Common.is22111()) gManager.register(Common.DEVICE_22111);
                if (Common.is23111()) gManager.register(Common.DEVICE_23111);
                try {
                    gManager.openSession();
                } catch(GlyphException e) {
                    Log.e(gManager.toString(), e.getMessage());
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                try {
                    gManager.closeSession();
                } catch (GlyphException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        gManager = GlyphManager.getInstance(getApplicationContext());
        gManager.init(gcallback);
    }

    @Override
    public void onDestroy()
    {
        try {
            gManager.closeSession();
        } catch (GlyphException e) {
            throw new RuntimeException(e);
        }
        gManager.unInit();
        super.onDestroy();
    }
}
```

まずこれをNothingPhoneにつないで動かして、起動できることを確認します
この時点ではGlyphインターフェースにはなにも変化はありません

## とりあえず光らせる
適当にボタンを配置して、ボタンを押すとPhone(2a)の左下のGlyphが光るようにしてみます
各Glyphの名前は、GlyphいじいじSDKのreadmeに掲載されています
![Frame 7](https://github.com/Nothing-Developer-Programme/Glyph-Developer-Kit/assets/56658376/8720123a-e88e-4958-96f3-2582b0dee590)
左下のやつはAのようなので、こんな感じに書きます


```java
button1 = findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ///ここが光らせる処理
                GlyphFrame.Builder builder = gManager.getGlyphFrameBuilder();
                GlyphFrame frame = builder.buildChannelA().build();
                gManager.toggle(frame);
            }
        });
```

![IMG_20240905_172142.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/fba0e864-6ed3-1e0f-1f00-451b6ffc1eb3.jpeg)

おおお！光った！！！！！

:::note warn
光らせる処理をonCreateとかonResumeに書けばアプリ起動すれば勝手に光らせられるのでは？と思い試したのですが、なぜかそれでは光りませんでした
なんで
:::

### 光らせる部分の長さを調節する

また、Phone(2a)のGlyphのCは光っている部分の長さを調節することができます
ハードウェア的には消灯含め25段階あるようです

適当にボタンを追加して、Cを半分だけ光らせるコードを書いてみます
```java
button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                GlyphFrame.Builder builder = gManager.getGlyphFrameBuilder();
                GlyphFrame frame = builder.buildChannelC().build();
                try {
                    //たぶん0~100で長さ調整できる
                    gManager.displayProgress(frame, 50);
                } catch (GlyphException e) {
                    throw new RuntimeException(e);
                }
            }
        });
```

![IMG_20240905_174416.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3634181/662d9f10-45da-a5a4-8d46-f74d6eb6f322.jpeg)

撮影環境の問題でよくわかりませんが、実物は下半分のみがしっかり点灯しています
オプションで上から伸ばしていくこともできるようです

:::note warn
SDKのreadmeにはCの中央や一部分だけ光らせる機能があるらしき記述がされているのですが、使い方がわからず試せていません
:::

### アニメーション
GlyphのBを「３秒間かけて明るさをゆっくり変化させた後に一秒待機を３回繰り返す」ようにするには

```java
button3 = findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                GlyphFrame.Builder builder = gManager.getGlyphFrameBuilder();
                GlyphFrame frame = builder.buildChannelB().buildInterval(1000).buildCycles(3).buildPeriod(3000).build();
                gManager.animate(frame);
            }
        });
```

こんな感じになります
（動画を張り付けるのが面倒だったので、実際の動作は君の目で確かめてください）

## 注意点

### フォアグラウンド動作限定？
readmeには「Only foreground applications are allowed to be used.」という記述があります
これが規約なのか技術的な制約なのかは不明ですが、Glyphをカスタマイズしまくって裏で常駐させるアプリには使えなさそうです。ぜひとも常駐できるようにしていただきたい
（ただし私の手元で試した限りだとWorkerを使ったバックグラウンド処理でも動きはしました）

### ライセンス不明
このSDK、公式サイトにもリポジトリにもライセンスの記載が見当たりません

## おわりに
発売日以降全く使っていなかったGlyph Interfaceがちょっとだけ輝きました