非SDK APIのテストについて

### はじめに
SDKが提供するAPIは`whitelist`,`greylist`, `blacklist`に分類され、
タイトルの非SDK APIとは`greylist`, `blacklist`のことを指します。
下記はそれぞれの意味を説明したものです。(公式から[引用](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#list-names))

* ブラックリスト
アプリの対象 API レベルに関係なく使用できない非 SDK インターフェース。アプリがこのインターフェースのいずれかにアクセスしようとすると、システムによってエラーがスローされます。

* グレーリスト
アプリの対象 API レベルで制限されていなければ使用できる非 SDK インターフェース。
Android 9（API レベル 28）から、各 API レベルで使用できる非 SDK インターフェースが制限されています。制限付きグレーリストの API へのアクセスは、API レベルがアプリの対象 API レベルより下の場合は可能ですが、対象 API レベルで制限されている非 SDK インターフェースにアプリがアクセスしようとすると、システムは API がブラックリストに登録されている場合と同じように動作します。
注: Android 9（API レベル 28）では、非制限付きグレーリストの非 SDK インターフェースは「ライトグレー リスト」、制限付きグレーリストの非 SDK インターフェースは「ダークグレー リスト」と呼ばれていました。

* ホワイトリスト
正式に文書化されている Android フレームワークのパッケージ インデックスの一部としてサポートされている、自由に使用できるインターフェース。


### テストについて
実際に自分達のアプリが非SDK APIを使用しているかどうか確認する手段は３通りあります。

* [Debuggableアプリ](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#test-debuggable-app)

Build Variantをdebugでアプリを走らせると以下のようにLog上で非SDK APIを検知できます。

`Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)`

通常Android Studioでプロジェクトを新規作成された際にはBuild Variantでdebugが自動で定義されますが、もしカスタムのBuild Variantを使用したい場合はbuild.gradelに以下の記述をすることでdebug同様にLog上で非SDK APIを検知することが可能です。

````
android {
    buildTypes {
        customDebugType {
            debuggable true
            ...
        }
    }
}
````
<br>

* [StrictMode API](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#test-strictmode-api)

StrictModeには２つのポリシーがあり、それぞれThread policy, VM policyと分類されますが、
ここでは詳しい説明は割愛します。（[policyについて](https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder#penaltyListener(java.util.concurrent.Executor,%20android.os.StrictMode.OnVmViolationListener))）
このAPIをコードに埋め込むことで非SDK APIをコード上で検知することが可能です。
実際の利用例は以下のような感じです。

````
if (BuildConfig.DEBUG) {
  // Enable StrictMode
  StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
    .detectFileUriExposure()
    .detectCleartextNetwork()
    .penaltyLog()
    .penaltyDeath()
    .build());
}
````

Log上でのアウトプットはこんな感じです。

````
| DEBUG/StrictMode(23134): StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1 |
|:--|
| DEBUG/StrictMode(23134):     at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk(StrictMode.java:1041) |
| DEBUG/StrictMode(23134):     at android.database.sqlite.SQLiteStatement.acquireAndLock(SQLiteStatement.java:219) |
| DEBUG/StrictMode(23134):     at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:83) |
| DEBUG/StrictMode(23134):     at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1829) |
| DEBUG/StrictMode(23134):     at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1780) |
| DEBUG/StrictMode(23134):     at com.test.data.MainActivity.update(MainActivity.java:87) |
```

<br>

* [veridex tool](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#test-veridex-tool)

最後に紹介するのがAnalyticsを目的として使用されているツールです。
このツールではベースとなるappモジュールからサードパーティライブラリで使用されている
非SDK APIを検知することが可能です。
但し、以下のような制限事項が設けられています。（以下は公式から[引用](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#test-veridex-tool)）

・JNI を介した呼び出しは検出できません。
・リフレクションを介した呼び出しのサブセットのみを検出できます。
・非アクティブなコードパスの解析は API レベルのチェックに制限されています。

Windows, Mac, Linux環境でそれぞれテストするためのインストラクションが公式に
掲載されていますが、掲載されているveridexのリンク先はmasterになるので、実際はテスト対象となるレポジトリに保管されているveridexを使用することになります。

https://android.googlesource.com/platform/prebuilts/runtime/

1. 任意のリポジトリを選択する
![Screen Shot 2020-04-22 at 0.37.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624769/66702f65-88a8-b5b2-a06b-786ed3254645.png)

2.`appcompat`を選択する
![Screen Shot 2020-04-22 at 0.38.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624769/3f2e9f9d-4954-afe6-78df-fd4eb6b2758a.png)

3. `tgz`を選択するとダウンロードが自動で始まります
![Screen Shot 2020-04-22 at 0.39.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624769/336aed60-6566-43e9-290e-11a62a238147.png)

後は[公式のインストラクション](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#veridex-windows)通りですが、お使いのMacによってはveridexがマルウェアに分類されて使えないことがあるかもしれないので、そんな時は表示されたダイアログのHelpからSystem Preference上で使用解除できる方法が記載されるかと思います。


そして、以下は実行結果の一部です。

````
#29: Reflection greylist Ljava/lang/reflect/AccessibleObject;->override use(s):
       Lcom/google/gson/internal/reflect/UnsafeReflectionAccessor;->getOverrideField()Ljava/lang/reflect/Field;

#30: Reflection greylist Lsun/misc/Unsafe;->allocateInstance use(s):
       Lcom/google/gson/internal/UnsafeAllocator;->create()Lcom/google/gson/internal/UnsafeAllocator;

#31: Reflection greylist Lsun/misc/Unsafe;->theUnsafe use(s):
       Lcom/google/gson/internal/UnsafeAllocator;->create()Lcom/google/gson/internal/UnsafeAllocator;
       Lcom/google/gson/internal/reflect/UnsafeReflectionAccessor;->getUnsafeInstance()Ljava/lang/Object;

31 hidden API(s) used: 0 linked against, 31 through reflection
       24 in greylist
       0 in blacklist
       1 in greylist-max-o
       6 in greylist-max-p
To run an analysis that can give more reflection accesses, 
but could include false positives, pass the --imprecise flag. 

````

この実行結果から対象アプリがAndroid 11で非SDK APIになるAPIが31つあるのが分かりました。