adbでAndroidのIMEIを取得する方法が大体間違っている件

## TL;DR
以下のように、 `iphonesubinfo` サービスに  `4` を渡すのがAndroid 8以降は正しい。

```ｓｈ
adb shell service call iphonesubinfo ４
```

よくある以下のやり方は、 **API Level 30（Android 11相当）以降で deprecated指定されるので注意が必要**

```ｓｈ
adb shell service call iphonesubinfo 1
```

以下、ちょっとだけ詳しい解説をします。

## そもそも一体何をしているのか
もう一度コードを貼ります。

```
adb shell service call iphonesubinfo ４
```

このうち `adb shell` についてはadbのshellにコマンドを引き渡すための記述に過ぎないので、実際にAndroidの中で実行されているのは `service call iphonesubinfo 4` となります。

## `service` とはなにか
`service` はAndroid上で動作しているサービスの操作を行うものです。Linuxにも同盟のコマンドはありますが、それとは似て非なるものと言えます。

`-h` で表示される文言は以下のようなものです。

```sh
Usage: service [-h|-?]
       service list
       service check SERVICE
       service call SERVICE CODE [i32 N | i64 N | f N | d N | s16 STR | null | fd f | nfd n | afd f ] ...
Options:
   i32: Write the 32-bit integer N into the send parcel.
   i64: Write the 64-bit integer N into the send parcel.
   f:   Write the 32-bit single-precision number N into the send parcel.
   d:   Write the 64-bit double-precision number N into the send parcel.
   s16: Write the UTF-16 string STR into the send parcel.
  null: Write a null binder into the send parcel.
    fd: Write a file descriptor for the file f to the send parcel.
   nfd: Write file descriptor n to the send parcel.
   afd: Write an ashmem file descriptor for a region containing the data from file f to the send parcel.
```

上記を見ると、`service call iphonesubinfo 4` の意味が見えてきます。
`call` はサービスの呼び出しを行い、`iphonesubinfo` はサービス名、 `4` はコードというわけですね。

## Android上で動作するサービス
Android上で動作するサービスの取得方法も先程のヘルプを見ればなんとなくわかりますね。そう、 `service list` です。やってみましょう。

```sh
Found 199 services:
0	DockObserver: []
1	SurfaceFlinger: [android.ui.ISurfaceComposer]
2	accessibility: [android.view.accessibility.IAccessibilityManager]
3	account: [android.accounts.IAccountManager]
4	activity: [android.app.IActivityManager]
5	activity_task: [android.app.IActivityTaskManager]
6	adb: [android.debug.IAdbManager]
7	alarm: [android.app.IAlarmManager]
8	android.hardware.identity.IIdentityCredentialStore/default: []
9	android.hardware.power.IPower/default: []
10	android.hardware.rebootescrow.IRebootEscrow/default: []
(以下省略)
```

ここで注目すべきは、多くのサービスがサービス名と合わせて、 `android.view.accessibility.IAccessibilityManager` のようなインタフェース名を公開していることです。実はこれらのファイルの多くはAndroidのソースコードに含まれており、例えば `IAccesibilityManager` は以下になります。

https://android.googlesource.com/platform/frameworks/base/+/d54f3f41c4b41955b7b4382a08b97a356b31fde4/core/java/android/view/accessibility/IAccessibilityManager.aidl

拡張子 `*.aidl` はAndroidのインタフェース定義言語（AIDL）を表し、名前通りそのAndroidサービスとやり取りする際のインターフェースが記述されています（記述はすごくJavaっぽいです）

## Androidサービスの呼び出し方

同様に `iphonesubinfo` を探すと、以下のコードが見つかります。

https://android.googlesource.com/platform/frameworks/base/+/master/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl

中身は以下のような感じです。

```java
interface IPhoneSubInfo {
    /** @deprecated Use {@link #getDeviceIdWithFeature(String, String) instead */
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    String getDeviceId(String callingPackage);
    /**
     * Retrieves the unique device ID, e.g., IMEI for GSM phones.
     */
    String getDeviceIdWithFeature(String callingPackage, String callingFeatureId);
     /**
     * Retrieves the unique Network Access ID
     */
    String getNaiForSubscriber(int subId, String callingPackage, String callingFeatureId);
    /**
     * Retrieves the unique device ID of a phone for the device, e.g., IMEI
     * for GSM phones.
     */
    String getDeviceIdForPhone(int phoneId, String callingPackage, String callingFeatureId);
    /**
     * Retrieves the IMEI.
     */
    String getImeiForSubscriber(int subId, String callingPackage, String callingFeatureId);
    /**
     * Retrieves the software version number for the device, e.g., IMEI/SV
     * for GSM phones.
     */
    String getDeviceSvn(String callingPackage, String callingFeatureId);

    //（中略）
}
```

ではこのAIDLのメソッドを呼び出すに当たり、最後の `4` という数字は何を意味するのか。
実はこの数字は、AIDLに定義されているメソッドの順番（1スタート）になっています。

したがって、 `service call iphonesubinfo 4` で呼び出されるメソッドは、 `getImeiForSubscriber` となります。そう、これがIMEIを取得するメソッドだったのです。

では `service call iphonesubinfo 1` で呼び出されるメソッドは、当然戦闘のメソッドですが、それは以下のようなアノテーションが付されています。

```java
    /** @deprecated Use {@link #getDeviceIdWithFeature(String, String) instead */
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    String getDeviceId(String callingPackage);
```

そう、30以降ではサポートされず、別のメソッドを使うように、と書いてあります。

ただ、そもそもその「別のメソッド」というのが `getImeiForSubscriber` じゃないじゃないか、という話もあると思います。使うべきとされているのはこちらのメソッドです。

```java
    /**
     * Retrieves the unique device ID, e.g., IMEI for GSM phones.
     */
    String getDeviceIdWithFeature(String callingPackage, String callingFeatureId);
```

はい、記述どおり、このメソッドはIMEIを返すことを保証しないのです。それは実は内部の実装依存です。したがってIMEIと明確に指定して抜き出したい場合には、 `getImeiForSubscriber` を、すなわち `service call iphonesubinfo 4` をコールするのが望ましい、という話になります。

おつかれさまでした。