Androidのライブ壁紙でOpenGL ES 3.2を動かす

## 概要

Androidにはリアルタイムかつデスクトップのタッチやスクロール等に対してインタラクティブに壁紙を描画出来る `ライブ壁紙` というシステムがあります。本記事ではこのライブ壁紙のシステム上で `OpenGL ES 3.2` で壁紙をリアルタイムに描画する手順をまとめています。

![ライブ壁紙](https://qiita-image-store.s3.amazonaws.com/0/88918/3172dd58-40b7-1df6-6b64-4703083b915d.gif)

### ライブ壁紙とは

ライブ壁紙とは `WallpaperService` というバックグラウンドで動作するアプリケーションとなります。アプリの実装者はサービスであるWallpaperServiceと描画エンジンである `WallpaperService.Engine` を継承しレンダリング処理を実装する事によりライブ壁紙を作成することになります。

WallpaperService.Engineでは壁紙のレンダリングターゲットであるSurfaceHolderが渡され、実装者は、このSurfaceHolderを使用し壁紙のレンダリングを行います。その他、WallpaperService.Engineではタッチイベントやデスクトップのスクロールイベント等のイベントをハンドリングし、そのステータスを壁紙のレンダリング内容をインタラクティブに反映する事ができます。

```java
public class MainWallpaperService extends WallpaperService {

    @Override
    public Engine onCreateEngine() {
        return new MainWallpaperEngine();
    }

    public class MainWallpaperEngine extends Engine {
        
        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
            // 描画面の生成
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
            // 描画面のステータス変更
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            // 描画面の破棄
        }
    }
}
```

普通のアプリケーションであればGLSurfaceViewのようなOpenGLの初期化を行ってくれるユーティリティが用意されているのですがライブ壁紙のシステムの上ではそのようなOpenGLの初期化を補助してくれるようなユーティリティは準備されておらず実装者自身でOpenGLの初期化を行う必要があります。

### ライブ壁紙でOpenGLを使用可能にするライブラリ

https://github.com/markfguerra/GLWallpaperService

実はライブ壁紙上でOpenGLの初期化を行ってくれるライブラリはOSSであれば既に存在します。しかし、上記のライブラリの実装を見る限りではOpenGL 1.x系にしか対応していません。なので今回はOpenGLの初期化を自力で書いてOpenGL ES 3.2を使えるようにしてみようと思います。

### OpneGLの初期化を実装するにあたり参考にする実装

https://android.googlesource.com/platform/frameworks/base/+/master/opengl/java/android/opengl/GLSurfaceView.java

AndroidはOSSなのでOSの中身を見ることが出来ます。したがってOpenGLを初期化しているであろうGLSurfaceViewの実装を参考にすればSurfaceHolderしかアプリケーションに渡してくれないWallpaperService.Engineでも、どうすればOpneGLを初期化出来るのかが見えてくると思います。

### OpenGLを初期化するためのAPIである「EGL」

![EGLロゴ](https://qiita-image-store.s3.amazonaws.com/0/88918/d4eb5c4a-f9d5-4281-ba4c-c7f7bf971e2e.png)

GLSurfaceViewのソースコードを見ていただければわかりますが、OpneGLの初期化にAndroidではEGLと呼ばれるAPIを使用しています。これはKronosグループが策定したOpenGLやOpenVGを初期化するためのAPIになります。

LinuxとかでもEGL用のパッケージをインストールすると使えたりします。

[NVIDIA Developer Blog - EGL Eye: OpenGL Visualization without an X Server](https://devblogs.nvidia.com/egl-eye-opengl-visualization-without-x-server/)

[GCPでOpenGLを使用しサーバ3Dレンダリング](https://qiita.com/as_kuya/items/17fbf56b4907da256029)

今回はこのAPIを使用してOpneGLを初期化します。

## 実装

それでは ライブ壁紙 + OpenGL ES 3.2の実装を解説していきたいと思います。

流れについては下記になります。

1. WallpaperServiceの作成
1. EGLのAPIの初期化
1. EGLコンテキスト、EGLサーフェスの初期化
1. OpenGLの描画処理の実装
1. EGLコンテキスト、EGLサーフェスの後処理
1. EGLのAPIの後処理
1. AndroidManifest.xmlにライブ壁紙のサービスを設定

なお実装はGitHubにおいてありますので宜しければ、そちらもご参考にしていただければ幸いです。

https://github.com/redlily/training-livewallpaper-with-opengl

### WallpaperServiceの作成

兎にも角にもライブ壁紙はWallpaperServiceが無いと始まらないのでWallpaperServiceを継承したサービスクラスを作成します。

```java
public class MainWallpaper extends WallpaperService {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public Engine onCreateEngine() {
        return new MainWallpaperEngine();
    }

    public class MainWallpaperEngine extends Engine {

        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);
            // TODO: EGLのAPIの初期化、レンダリングスレッドの作成、ピクセルフォーマットの設定を実装
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
        }

        @Override
        public void onSurfaceChanged(final SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
            // TODO: EGLによるOpenGLの初期化、width、heightの保存を実装
        }

        @Override
        public void onOffsetsChanged(final float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) {
            super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);
            // TODO: デスクトップのスクロールイベントを処理、今回はこのイベントをトリガーとして再描画を実装
        }

        @Override
        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
            super.onSurfaceRedrawNeeded(holder);
            // TODO: 再描画要求に対する処理を実装
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            // TODO: EGLやOpenGLの後処理を実装
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            // TODO: レンダリングスレッドのシャットダウン
        }
}
```

とりあえず、今回のサンプルプログラムに必要なメソッドをoverrideし、方針をコメントで書きました。

初期化、後処理の順番、実装方法等は各々のアプリケーションの方針等で変わってくると思うので私の実装は参考程度に。

### EGLのAPIの初期化

まずはEGLのAPIを初期化してEGLを使用可能な状態にしておきます。それと合わせてSurfaceHolderのフォーマットの設定とレンダリングスレッドの作成も合わせて行います。

OpneGLを実行するスレッドはメインスレッドでも初期化可能ですがメインスレッドはイベントを受けるスレッドですし、WallpaperService.Engineが複数生成される動作状況もあり、メインスレッドでOpneGLの初期化を行ってしてしまうとWallpaperService.Engine同士で処理が競合してしまうので各々のWallpaperService.Engine専用のレンダリングスレッドを作成しておきます。

このサンプルではスレッディングを簡単に実装するためにHandlerThreadクラスとHandlerを使用してレンダリングスレッドを作成していますが厳密且つ高性能に作りたいのであればThreadを生で使うかハイパフォーマンスのスレッディングライブラリ（例えばReactiveXとか）を使用することをお勧めします。

```java
@Override
public void onCreate(SurfaceHolder holder) {
    super.onCreate(holder);

    // EGLを初期化
    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    int[] major = new int[1];
    int[] minor = new int[1];
    boolean status = eglInitialize(eglDisplay, major, 0, minor, 0);
    if (status) {
        this.eglDisplay = eglDisplay;
    } else {
        Log.e(TAG, "Failed to initialize EGL.");
    }

    // レンダリング用のスレッドを生成
    renderingThread = new HandlerThread(toString());
    renderingThread.start();
    renderingHandler = new Handler(renderingThread.getLooper());

    // スレッド制御用のロックオブジェクトも作っておく
    lock = new Object();

    // 初期化しやすいようにピクセルフォーマットを固定値で設定しておく
    holder.setFormat(PixelFormat.RGB_565);
}
```

本来はEGL14.eglGetDisplayといった感じで書くのですがEGL14やGLES32のクラスのstaticメソッドをクラス名をいちいち書いて呼び出すのが煩わしいのでstatic importを使用しています。 

###  EGLコンテキスト、EGLサーフェスの初期化

いよいよEGLを使用したOpneGLを初期化を実装していきます。

この段階で予め作成したレンダリングスレッドで初期化を行います。初期化、後処理が連続で発行されレンダリングスレッドで処理が追いつかない場合を想定し念の為、メインスレッドをロックして初期化処理が完了するまでメインスレッドを待機させるように実装しています。

```java
@Override
public void onSurfaceChanged(final SurfaceHolder holder, int format, int width, int height) {
    super.onSurfaceChanged(holder, format, width, height);

    this.width = width;
    this.height = height;

    synchronized (lock) {
        renderingHandle.post(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    // EGL関連の初期化
                    if (!initializeEGL(holder)) {
                        return;
                    }

                    // OpenGL関連の初期化
                    initializeGL();
                    drawGL();

                    // EGL、OpenGLの初期化が完了した事を通知
                    lock.notify();
                }
            }
        });

        // EGL、OpneGLの初期化が完了するまで待つ
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

レンダリングのターゲットとなるEGLサーフェス、レンダリングに必要なステータス、バッファ、テクスチャ、シェーダ等のインスタンスを管理するEGLコンテキスト、これらを生成し、スレッドにバインドしてOpenGLを使用できる状態にします。

```java
private boolean initializeEGL(SurfaceHolder holder) {
    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

    // すでにEGL関連のインスタンスがある場合は開放
    if (eglSurface != EGL_NO_SURFACE) {
        eglDestroySurface(eglDisplay, eglSurface);
        eglSurface = EGL_NO_SURFACE;
    }
    if (eglContext != EGL_NO_CONTEXT) {
        eglDestroyContext(eglDisplay, eglContext);
        eglContext = EGL_NO_CONTEXT;
    }

    // EGL configurationを取得
    int[] configAttrs = new int[]{
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_RED_SIZE, 5,
            EGL_GREEN_SIZE, 6,
            EGL_BLUE_SIZE, 5,
            EGL_DEPTH_SIZE, 16,
            EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,
            EGL_NONE
    };
    EGLConfig[] config = new EGLConfig[1];
    int[] numConfigs = new int[1];
    boolean status = EGL14.eglChooseConfig(eglDisplay, configAttrs, 0, config, 0, 1, numConfigs, 0);
    if (!status || numConfigs[0] == 0) {
        Log.e(TAG, "Failed to choose a EGL configuration.");
        return false;
    }

    // EGL surfaceの生成
    eglSurface = eglCreateWindowSurface(eglDisplay, config[0], holder.getSurface(), null, 0);
    if (eglSurface == EGL_NO_SURFACE) {
        Log.e(TAG, "Failed to create a EGL window surface.");
        return false;
    }

    // EGL contextの生成
    int[] contextAttrs = new int[]{
            EGL_CONTEXT_CLIENT_VERSION, 3,
            EGL_NONE
    };
    eglContext = eglCreateContext(eglDisplay, config[0], EGL_NO_CONTEXT, contextAttrs, 0);
    if (eglContext == EGL_NO_CONTEXT) {
        Log.e(TAG, "Failed to create a EGL context.");
        return false;
    }

    // EGLをバインドしてOpenGLを有効化する
    return eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
}
```

まずはSurfaceHolderをターゲットに赤5bit、緑6bit、青5bit、デプスバッファ16bit、OpneGL ES 3.x系で初期化が可能かをEGLに問い合わせてEGLコンフィグを取得出来るかどうかでEGLサーフェスが生成可能かを判定します。

```java
// EGL configurationを取得
int[] configAttrs = new int[]{
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 5,
        EGL_GREEN_SIZE, 6,
        EGL_BLUE_SIZE, 5,
        EGL_DEPTH_SIZE, 16,
        EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,
        EGL_NONE
};
EGLConfig[] config = new EGLConfig[1];
int[] numConfigs = new int[1];
boolean status = EGL14.eglChooseConfig(eglDisplay, configAttrs, 0, config, 0, 1, numConfigs, 0);
if (!status || numConfigs[0] == 0) {
    Log.e(TAG, "Failed to choose a EGL configuration.");
    return false;
}
```

そしてEGLサーフェスの生成が可能であればSurfaceHolderをレンダリングターゲットとしたEGLサーフェスの生成します。

```java
// EGL surfaceの生成
eglSurface = eglCreateWindowSurface(eglDisplay, config[0], holder.getSurface(), null, 0);
if (eglSurface == EGL_NO_SURFACE) {
    Log.e(TAG, "Failed to create a EGL window surface.");
    return false;
}
```

次にOpenGL ES 3.xをOpenGLのクライアントのバージョンとして指定してEGLコンテキストを作成します。

```java
// EGL contextの生成
int[] contextAttrs = new int[]{
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE
};
eglContext = eglCreateContext(eglDisplay, config[0], EGL_NO_CONTEXT, contextAttrs, 0);
if (eglContext == EGL_NO_CONTEXT) {
    Log.e(TAG, "Failed to create a EGL context.");
    return false;
}
```

最後にこれらのEGLサーフェスとEGLコンテキストをバインドします

```java
// EGLをバインドしてOpenGLを有効化する
status = eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
if (!status) {
    Log.e(TAG, "Failed to make the EGL surface and the EGL context.");
}
return status;
```

eglMakeCurrentが実行された時に、このメソッドを呼び出したスレッドにOpenGLのコンテキストが紐付きます。

実はEGLコンテキストとEGLサーフェスの生成処理はレンダリングスレッド外の例えばメインスレッドで行っても実は差し支えありません。しかしOSの電源管理機能等によりEGLコンテキストがロストした場合はレンダリングスレッドで復帰処理の実装を考えるとレンダリングスレッドで実行する方が楽なのでこのような実装になっています。

### OpenGLの描画処理の実装

これでライブ壁紙上でOpenGL ES 3.xが使える状態になったので、後は煮るなり焼くなり好きに出来る状態になりました。OpenGL ES 3.xをターゲットとして初期化したのでGLSL ES 3.0等のOpneGL ES 3.x系からでしか使用できない処理ももちろん使用可能な状態になっています。

OpneGL ES 3.xによるレンダリングの解説は割愛します。詳細な実装は下記のリポジトリをご参照してください。

https://github.com/redlily/training-livewallpaper-with-opengl

```java
private void initializeGL() {
    // バッファ、テクスチャ、シェーダ、ステータスの初期化等々
    glViewport(0, 0, (int) width, (int) height);
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

    // シェーダプログラム生成
    try (InputStream vs = getAssets().open("shader.vert");
            InputStream fs = getAssets().open("shader.frag")) {
        shaderProgram = GLES32Utils.createProgram(vs, fs);
        uniformModelMatrix = glGetUniformLocation(shaderProgram, "uModelMatrix");
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 描画物生成、X, Y, Z, R, G, B, A
    modelVertices = GLES32Utils.createBuffer(GL_ARRAY_BUFFER, new float[]{
            0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
            (float) Math.sin(2.0 * Math.PI / 3), (float) Math.cos(2.0 * Math.PI / 3), 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
            (float) Math.sin(-2.0 * Math.PI / 3), (float) Math.cos(-2.0 * Math.PI / 3), 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
    }, GL_STATIC_DRAW);
    modelIndices = GLES32Utils.createBuffer(GL_ELEMENT_ARRAY_BUFFER, new int[]{
            0, 1, 2
    }, GL_STATIC_DRAW);
}

private void drawGL() {
    // コンテキストのロストチェック
    if (eglGetError() == EGL_CONTEXT_LOST) {
        if (!initializeEGL(getSurfaceHolder())) {
            return;
        }
        initializeGL();
    }

    // OpenGL ESによる描画処理
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shaderProgram);

    float[] modelMatrix = new float[16];
    Matrix.setIdentityM(modelMatrix, 0);
    Matrix.scaleM(modelMatrix, 0, 1.0f, width / height, 1.0f);
    Matrix.rotateM(modelMatrix, 0, rot, 0.0f, 0.0f, 1.0f);
    glUniformMatrix4fv(uniformModelMatrix, 1, false, modelMatrix, 0);

    glEnableVertexAttribArray(INPUT_POSITION);
    glEnableVertexAttribArray(INPUT_COLOR);

    glBindBuffer(GL_ARRAY_BUFFER, modelVertices);
    glVertexAttribPointer(INPUT_POSITION, 3, GL_FLOAT, false, 4 * 7, 0);
    glVertexAttribPointer(INPUT_COLOR, 4, GL_FLOAT, false, 4 * 7, 4 * 3);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelIndices);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);

    glDisableVertexAttribArray(INPUT_POSITION);
    glDisableVertexAttribArray(INPUT_COLOR);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glUseProgram(0);

    glFlush();

    // 描画サーフェスのスワップ
    eglSwapBuffers(eglDisplay, eglSurface);
}

private void terminateGL() {
    // バッファ、テクスチャ、シェーダ、ステータスの後処理等々   
    GLES32Utils.deleteBuffer(modelVertices);
    GLES32Utils.deleteBuffer(modelIndices);
    glDeleteProgram(shaderProgram);
}
```

ここまでの実装で下記のようなレンダリング結果が表示されるようになっています。

![サンプルのライブ壁紙](https://qiita-image-store.s3.amazonaws.com/0/88918/ebb9c65d-11fe-10df-b0f6-96f89d15fccb.png)


ここまでの実装でEGLを使用してOpneGLを使える状態まで初期化できたわけですが最後にOpneGLの描画前と描画後にEGLを使用して実装しなければならない事があります。

それは描画前の `EGLコンテキストのロストチェック` と `画面のスワップ` です。

まずは、EGLコンテキストのロストチェックです。

```java
if (eglGetError() == EGL_CONTEXT_LOST) {
    if (!initializeEGL(getSurfaceHolder())) {
        return;
    }
    initializeGL();
}
```

EGLコンテキストのロストとはどういった状態なのでしょうか？

EGLのリファレンスによるとコンテキストロストはOSの電源管理処理等によりGPUのステータスが変化した場合に発生するようです。WebGLの `webglcontextlost` というイベントが発生した状態、DirectXのデバイスロスト、デバイスリムーブ、デバイスリセットという状態に該当するものです。

EGLのコンテキストがロストした場合、EGLコンテキスト、EGLサーフェス、OpneGL、これらを再度初期化し直す必要があります。

そして、次は描画の内容をEGLサーフェス(ここではSurfaceHolder)に書き出す処理を実装します。

```java
// 描画サーフェスのスワップ
eglSwapBuffers(eglDisplay, eglSurface);
```

これはEGLサーフェスに書き込まれたレンダリング結果をSurfaceHolderに適用する操作になります。これを行わないと実際にOpneGLでレンダリングした結果が画面に現れません。

これでここまで実装できれば、最後は描画タイミングはの実装になります。

```java
@Override
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
    super.onSurfaceRedrawNeeded(holder);
    Log.d(TAG, "onSurfaceRedrawNeeded");
    renderingHandle.post(new Runnable() {
        @Override
        public void run() {
            drawGL();
        }
    });
}

@Override
public void onOffsetsChanged(
    final float xOffset, float yOffset,
    float xOffsetStep, float yOffsetStep,
    int xPixelOffset, int yPixelOffset) {

    super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);
    Log.d(TAG, "onOffsetsChanged");

    renderingHandle.post(new Runnable() {
        @Override
        public void run() {
            drawGL();
        }
    });
}
```

レンダリングタイミングは定期実行にしたり、何かのイベントをトリガーにしたりとアプリケーションの要件に沿った実装にすると良いです。

### EGLコンテキスト、EGLサーフェスの後処理

EGLコンテキスト、EGLサーフェスの後処理は簡単、WallpaperService.Engineのイベントに沿って開放処理を書くだけになり初期化と比べるとすごく楽な実装になります。

こちらもEGLコンテキスト、EGLサーフェスの初期化処理と同様の理由によりメインスレッドをロックしてEGLの後処理が完了するまでメインスレッドを待機させます。

```java
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
    super.onSurfaceDestroyed(holder);

    synchronized (lock) {
        renderingHandle.post(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    terminateEGL();

                    // EGL、OpenGLの後処理が完了した事を通知
                    lock.notify();
                }
            }
        });

        // EGL、OpenGLの後処理が完了するまで待つ
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private void terminateEGL() 
    // OpenGL関連の開放
    terminateGL();

    // EGL関連の開放
    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

    if (eglSurface != EGL_NO_SURFACE) {
        eglDestroySurface(eglDisplay, eglSurface);
        eglSurface = EGL_NO_SURFACE;
    }
    if (eglContext != EGL_NO_CONTEXT) {
        eglDestroyContext(eglDisplay, eglContext);
        eglContext = EGL_NO_CONTEXT;
    }
}
```

### EGLのAPIの後処理

最後にWallpaperService.Engineが破棄されるタイミングでEGLの後処理、レンダリングスレッドのシャットダウン処理を行い、これでキレイに片付けを行います。

```java
@Override
public void onDestroy() {
    Log.d(TAG, "onDestroy");
    super.onDestroy();

    renderingThread.quit();

    // EGLを開放
    if (eglDisplay != EGL_NO_DISPLAY) {
        eglTerminate(eglDisplay);
        eglDisplay = EGL_NO_DISPLAY;
    }
}
```

### ライブ壁紙をAndroidManifest.xmlに登録

そして実装が出来上がったら実際にライブ壁紙として設定できるように実装を行ったWallpaperServiceをAndroidManifest.xmlに設定します。

#### XMLの用意

まずはres/xmlディレクトリにライブ壁紙のメタ情報を記述したXMLを用意します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@mipmap/ic_launcher"
    android:description="@string/app_name"
    android:settingsActivity="com.example.livewallpaper.MainActivity"
    />
```

設定項目は次の通りになります。

- thumbnail - ライブ壁紙のサムネイル
- description - ライブ壁紙の説明
- settingsActivity - ライブ壁紙の設定用のActivity

#### Android Manifestの編集

そして作成したXMLとWallpaperServiceをAndroidManifest.xmlに追加します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.livewallpaper">

    <!-- ライブ壁紙の動作を要求する -->
    <uses-feature
        android:name="android.software.live_wallpaper"
        android:required="true"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!-- ライブ壁紙用のアクティビティ (必要なら) -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        
        <!-- ライブ壁紙用のサービス -->
        <service
            android:name=".MainWallpaper"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_WALLPAPER">
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService"/>
            </intent-filter>

            <!-- ここに先程、作成したXMLを指定 -->
            <meta-data
                android:name="android.service.wallpaper"
                android:resource="@xml/main_wallpaper"/>  
        </service>

    </application>
</manifest>
```

## 最後に

EGLによるライブ壁紙のシステム上でのOpneGL ES 3.xを使えるようにするまでの実装手順はいかがでしょうか？

筆者は、ほぼ新人の時の7,8年前(2010年ごろ)、Androidで言えばバージョン1.6, 2.1, 2.2あたりが普通であり、端末で言えばXperia X10、IS03、HTC Desireが日本を席巻していた頃に仕事でOpenGL(この頃は1.x系)を使用したライブ壁紙を開発する案件に巻き込まれて情報がほとんどない状態で右も左もわからない状態(3Dプログラミングは出来たが)で苦難しながらGLSurfaceViewのソースコードを解析しEGLの使用方法を会得しOpneGLの初期化処理を書いて、なんとか納品したのはいい思い出でした。

今でもAndroid上でEGLを使用してOpneGLを初期化する方法は情報が少が少ないので(特に日本語)、この記事の知識が少しでも役にたてば幸いです。

### 今後の発展

glTFとかVRMとかライブ壁紙上で動かせたら面白いよね？ と思いglTFをライブ壁紙上で動かす何かを開発中。いつかアプリとしてリリースできたら良いね。

![ライブ壁紙](https://qiita-image-store.s3.amazonaws.com/0/88918/3172dd58-40b7-1df6-6b64-4703083b915d.gif)

とりあえず、シェーダは最低限、アニメーションはスキニングだけでモーフィングは実装中