Android15の録画検出機能を試した話

# はじめに
[CYBIRD Advent Calendar 2024](https://qiita.com/advent-calendar/2024/cybird)の2日目担当の@chikako_ikedaです。
スマホアプリを専門に担当するチームにてAndroid側の担当をしています。
1日目は@cy_hinano_imaiさんの「[GASとChatGPTで簡単なゲームを作ってみる](https://qiita.com/cy_hinano_imai/private/0cf1faf9f5522a61c159)」でした。
生成AIで簡単なゲームが作れる時代になっている中、あえてのネイティブのお話を用意してみました。

# 概要
もはや「Androidの人」と化しつつある中、今年も相変わらずAndroidのネタをお送りすることにしました。
「Android15から画面録画の検知をアプリでできるようになったので、どんなことができるのか試してみた」となります。


# 本題
## 画面キャプチャや録画の検知は、需要があるのか?
新機能として紹介されたからと、試すほどの機能か？　と疑問に思う方が多いかもしれません。
使ってみたかっただけだろ、と…
しかし、画面をキャプチャ/録画することを検知できることは、意外と需要があるのです。
より正確に言うと **「キャプチャや録画されたくない画面があるので、制御したい」** という時に需要があります。
なお、Androidは画面キャプチャを制御する機能に関しては初期から提供されていました。

:::note info
iOSではキャプチャ制御の機能は提供されていないとのこと。
「OSで機能差分を出したくない」という要望もそれなりに多いので、「実装しない」という判断になることもあります。
:::

## 検知してみよう
というわけで「何ができて、どんな値が取れるのか」を確認してみました。
公式ドキュメントに記載があるのですが、どんな値が取れるのか？　なんてことは書いてないので、試してみるのが早いのです。

### 実装
実際に検知するための簡単なアプリを作ってみました。

以下を追加していきます。
実装方法自体はドキュメントに記載がありましたが、工程自体はそう多くないです。
※新規でプロジェクト作ったら、問答無用でKotlinにされました… というわけで、実装言語はKotlinです。

#### 1. AndroidManifest.xmlに「DETECT_SCREEN_RECORDING」権限を追加する
画面録画を検知するCallback関数を使用するためには、「DETECT_SCREEN_RECORDING」という権限が必要になります。
そのため、AndoridManifest.xmlに以下を追加します。
<uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />

追加すると、こんな感じ。
``` 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">

    <!-- ここに追加 -->
    <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
     
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SampleApp35"
        tools:targetApi="35">
       (略)
</manifest>
```
今のところRuntime Permissionではないので、ユーザに承認を取る必要はありませんが、今後の使われ方次第で変わるかもしれません。
どの程度、気軽に使っていいのかは手探りになりそうです。

#### 2. 画面録画を検知したいActvityでCallback関数を追加する
録画されていることを検知するためのCallback関数を画面に追加します。
とはいえ、ドキュメント通りに実装するだけでOKです。
まずはCallbackとしてComsumerを実装します。

```Kotlin
    // Consumerは「java.util.function.Consumer」の方を使います
    private val mCallback = Consumer<Int> {state ->
        if (state == WindowManager.SCREEN_RECORDING_STATE_VISIBLE) {
            // 画面録画を検知した場合はこちらに入ってくる
        } else {
            // 画面録画を終了した場合はこちら。
        }

    }
```
次に、onStartとonStopでそれぞれCallbackを追加したり外したりします。
addScreenRecordingCallbackとremoveScreenRecordingCallbackはAPI Level 35で追加された関数なので、アノテーションをつけておいた方が安全です。
```Kotlin
    @RequiresApi(35)
    override fun onStart() {
        super.onStart()
        val initialState =
            windowManager.addScreenRecordingCallback(mainExecutor, mCallback)
        mCallback.accept(initialState)
    }

    @RequiresApi(35)
    override fun onStop() {
        super.onStop()
        windowManager.removeScreenRecordingCallback(mCallback)
    }
```
これだけです。簡単ですね。

ただ、CallbackになっているConsumerはIntegerで指定されているため、取得できるのは「録画しているか」「録画していないか」のステータスのみでした。
なお、これで実装するとonStartで動かしてるため、アプリ起動した瞬間も「録画してないよ」って値が取れたりします。

### 動作確認
早速 **Android15の端末**で動作確認しましょう。
画面録画中かどうかのステータスが出るようにしてみた時のキャプチャを撮ってみました。
(動画は撮ったには撮ったんですが、Qiitaにアップロードできなかったので…)
なお、動画も撮ろうと作業している中で気づきましたが、「端末で録画していないと検知できない」のでAndroidStudioのデバッグ用録画機能使うと検知できませんでした。
動作を録画したい方はもう一つ録画できるカメラが必要です。

アプリ起動時
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2294629/fa6181f8-2269-14ff-d658-39dcb5f6310b.png" />

録画中
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2294629/e3725599-eb81-f194-b116-0ffc2ca44484.png" />

録画終了
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2294629/98b33a4c-ff54-7fae-bdb7-107c8628cf3c.png" />


## この機能、どう使うか
実装して「録画しているか」のステータスが取れることはわかりました。
しかし、この機能はどう使うのがいいのか、は実装してみてもネガティブな案しか思い浮かびませんでした。
機能の需要としては、「録画しないでほしい」という時の方が多そうです。
今回は文字の描画だけでしたが、画面をグレーアウトして録画の邪魔をすることも可能かと。
否定的な使い方のほかは、ネタ機能として実装するのはどうだろう…？
何某かのキャラクターが画面を埋め尽くして、元の画面は録画させない、とかだったらユーザとしては嫌な思いしないで済むのでは…？

また、「元の画面を見せないようにすることで録画をさせない」という方法を提示していますが、「録画を止める」ことはできません。

# 最後に
地味な調査ではありますが、時折こういう機能調査が必要になることがあります。
ついでに楽しく学べるといいですよね。

明日のCYBIRD Advent Calendar 2024 3日目は、@tomoko_ishizakaさんの「Dockerって結局よくわからない...という人のための入門者向けDocker紹介」です。
お楽しみに！