【Android】暗黙的インテントを受け取るにはどうすればいいのか

## はじめに
Androidアプリでは、Activityや他アプリのサービスを起動するときにIntentを使います。
自分自身Intentについてふわっとした理解だったので、Intentについて整理をしつつ、暗黙的Intentをどうやって受け取るようにするのかについてまとめたいと思います

## Intentは何者なのか
[公式ガイド](https://developer.android.com/guide/components/intents-filters?hl=ja&_gl=1*b0cwmu*_up*MQ..*_ga*MTYyNTEyNTAwMy4xNzMzNjM3MzA1*_ga_6HH9YJMN9M*MTczMzYzNzMwNC4xLjAuMTczMzYzNzMwNC4wLjAuMjAxNTM4MjczNQ..#kotlin)では、Intentは「別のアプリ コンポーネントからのアクションをリクエストするために使用できるメッセージング オブジェクト」と紹介されています。
>Intent は、別のアプリ コンポーネントからのアクションをリクエストするために使用できるメッセージング オブジェクトです。

コンポーネントとは、ActivityやService、BroadcastReceiver等のアプリで使っている部品のことですね。
IntentはActivity、Service、BroadcastReceiverに対して「アクションを実行して欲しい」というメッセージを送るために使用されるオブジェクトになります。

アクションというのは、例えば以下のようなことです。
- アプリ内の特定のActivityを起動してほしい
- カメラアプリを起動してほしい
- 他のアプリにデータを渡してほしい

アプリ内のコンポーネント起動や、他のアプリのコンポーネント起動でも、Intentを介してリクエストを行います。

## 2種類のIntent
Intentには２種類あり、**明示的Intent**と**暗黙的Intent**があります。

#### 明示的Intent
特定のコンポーネントを明示的に指定するIntentです。
主にアプリ内で他のコンポーネントを呼び出すときに使うIntentになります。
以下のようなActivity起動で使う機会が多く、使うことも多いものかと思います。
``` 明示的Intent.kt
// 特定のコンポーネント（CalledActivity）を明示的に指定して呼び出す
  val intent = Intent(context, CalledActivity::class.java).apply {
                    putExtra(Intent.EXTRA_TEXT, "Internal App")
                }

   context.startActivity(intent)
```


#### 暗黙的Intent
特定のコンポーネントを明示的に指定せず、アクションやカテゴリを指定するIntentです。
起動するコンポーネントAndroidのシステムが側で決定するようになっており、リクエスト時に指定したアクションやカテゴリをもとに適切なものを選択するようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512668/7a3c3d93-0986-6951-41d6-611771f47e1a.png)
([こちらの公式ガイド](https://developer.android.com/guide/components/intents-filters?hl=ja&_gl=1*b0cwmu*_up*MQ..*_ga*MTYyNTEyNTAwMy4xNzMzNjM3MzA1*_ga_6HH9YJMN9M*MTczMzYzNzMwNC4xLjAuMTczMzYzNzMwNC4wLjAuMjAxNTM4MjczNQ..#Types)から借用)

以下は、他のアプリ（メッセージアプリやメールアプリなど）にテキストメッセージを送る暗黙的Intentになります。
```暗黙的Intent.kt
 val intent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "text/plain"
                    addCategory(Intent.CATEGORY_DEFAULT)
                    putExtra(Intent.EXTRA_TEXT, "Message")
                }

 context.startActivity(intent)
```
startActivityを実行すると、Androidシステムが適切なコンポーネントを起動するように動きます。
適切なコンポーネントが複数ある場合は、ユーザーが使用するものを選べるようにダイアログを出してくれます。
サンプルコードの暗黙的Intentを実行すると以下のような画面が出てきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512668/7040702e-67e1-5cae-4e90-9043eadc619f.png" width=40% height=40%>


## 暗黙的Intentを受け取れるようにするには
暗黙的IntentはAndroidシステム側が適切なものを選択するとのことですが、Androidシステムがどういう判断で選んでいるのか。

それは、インストールされている各アプリのAndroidManifestのインテントフィルター（intent-filter）です。
暗黙的Intentで設定されたカテゴリ、アクション、データに一致するインテントフィルターを持つアクティビティやサービスを検索し、マッチするものを選択します。
もしマッチするものが複数ある場合は、前述したスクショのようなダイアログでユーザーに提示するようになっています。

実際に下記の暗黙的Intentを受け取れるActivityを自作してみようと思います。
```暗黙的Intent.kt
 val intent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "text/plain"
                    addCategory(Intent.CATEGORY_DEFAULT)
                    putExtra(Intent.EXTRA_TEXT, "Message")
                }

 context.startActivity(intent)
```

#### Activityの作成
下記は暗黙的Intentで呼び出されるActivityになります。
IntentのPutExtraで設定されるテキストを取得し、画面に表示するようになっています。
``` 暗黙的Intentで呼び出されるActivity.kt
class CalledActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        val text = intent.getStringExtra(Intent.EXTRA_TEXT).toString()
        setContent {
            CalledAppTheme  {
                Scaffold(modifier = Modifier.fillMaxSize()) {
                    Box(
                        contentAlignment = Alignment.Center,
                        modifier = Modifier.padding(it).fillMaxSize()
                    ) {
                        Text(text)
                    }
                }
            }
        }
    }
}
```

次に、このActivityをAndroidシステムの検索でマッチするように、インテントフィルターを定義したいと思います。

#### AndroidManifestにインテントフィルターを定義
暗黙的Intentを受け取りたいActivity（CalledActivity）にインテントフィルターの定義を設定しました。
受け取りたいIntentに沿ってaction、category、dataを設定する必要があります。
今回受け取りたいIntentは、以下を想定しているので、満たすように定義しています。
- アクション：ACTION_SEND
- カテゴリー：CATEGORY_DEFAULT
- データ：text/plain

```AndroidManifest.xml
   <activity
        android:name=".CalledActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.SEND" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain" />
        </intent-filter>
    </activity>
```

注意してほしいのが **android:exported** の部分です。
これは当該のActivityを外部アプリから呼び出せるかどうかの設定となっており、true：呼び出し可能、false：呼び出し不可（アプリ内部のみ可）を定義します。
今回は外部アプリの暗黙的Intentを受け取れるようにしたいので、trueに設定しています。
これをfalseにすると受け取れませんのでご注意ください。
（exportedの設定はAndroid 12以降では必須になっています。）

#### 暗黙的Intentを起動してみる
実際に暗黙的Intentを起動してみると、先ほど作ったActivityがダイアログの候補として表示され、無事Androidシステムが拾ってくれていることを確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512668/1d20470e-f9a4-ae2b-b306-075a2c0e8e36.png" width=40% height=40%>

CalledAppを選択すると、作成したAcitivityを開き、Intentからメッセージ取得して画面に表示されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512668/7680197b-55b1-fe30-4e13-738453685bc3.png" width=40% height=40%>

## おまけ
暗黙的Intentのアクションですが、独自に定義することも可能です。
独自をアクションを定義すると、暗黙的Intentでも、特定のアプリのコンポーネントを起動することができます。
独自のアクションを定義する際のルールとしては、必ずプレフィックスとしてアプリのパッケージ名を必ず含めてユニークなものにする必要があります。

``` 独自のアクションを定義.xml
 <activity
        android:name=".CalledActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="com.example.calledapp.action.open" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain" />
        </intent-filter>
    </activity>
```

``` 独自のアクションを使用した暗黙的Intent.kt
val intent = Intent().apply {
                    action = "com.example.calledapp.action.open"
                    type = "text/plain"
                    putExtra(Intent.EXTRA_TEXT, "独自のアクションで呼び出す")
                }

context.startActivity(intent)
```

上記暗黙的Intentを起動すると、起動アプリを選択するダイアログは表示されず、すぐに指定したActivityを起動します。


## おわり
私自身何となく使っていたIntentについて、公式ガイドを参照しながら自分なりに整理しました。
暗黙的Intentがどのように実行されているのかや、独自アクションを使った方法など、これまで知らなかったことが多く、新しい発見がありました。
特に独自アクションは初耳で、どのような場面で使うのか具体的なイメージはまだ湧いていません。
ただ、協業するアプリ間でのデータのやり取りや特定の機能を呼び出すときに役立つのかもと思いました。