データアクセスを監査する

データアクセスの監査を実行すると、アプリとその依存関係がユーザーのプライベート データにどのようにアクセスするかを把握できます。このプロセスは、Android 11(API レベル 30)以降を搭載しているデバイスで使用でき、予期しないデータアクセスの可能性の特定に役立ちます。アプリでは AppOpsManager.OnOpNotedCallback のインスタンスを登録して、次のいずれかのイベントが発生するたびにアクションを実行できます。

データアクセスの監査は、データがリクエストされたスレッドで起動されます。つまり、アプリ内のサードパーティ SDK やライブラリがプライベート データにアクセスする API を呼び出すと、データアクセスの監査によって OnOpNotedCallback が呼び出しに関する情報を調べられるようになります。通常、このコールバック オブジェクトは、アプリからの呼び出しか、SDK からの呼び出しかを、現在のスレッドのスタック トレースなど、アプリの現在のステータスを調べて判別できます。

データアクセスをログに記録する

AppOpsManager.OnOpNotedCallback のインスタンスを使ってデータアクセスの監査を行うには、アクティビティの onCreate() メソッド内またはアプリの onCreate() メソッド内など、データアクセスを監査するコンポーネントにコールバック ロジックを実装します。

次のコード スニペットでは AppOpsManager.OnOpNotedCallback を定義して、1 つのアクティビティ内でデータアクセスを監査しています。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
        private fun logPrivateDataAccess(opCode: String, trace: String) {
            Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\nStack Trace:\n$trace")
        }

        override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
            logPrivateDataAccess(
                    syncNotedAppOp.op, Throwable().stackTrace.toString())
        }

        override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
            logPrivateDataAccess(
                    syncNotedAppOp.op, Throwable().stackTrace.toString())
        }

        override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.message)
        }
    }

    val appOpsManager =
            getSystemService(AppOpsManager::class.java) as AppOpsManager
    appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState,
        @Nullable PersistableBundle persistentState) {
    AppOpsManager.OnOpNotedCallback appOpsCallback =
            new AppOpsManager.OnOpNotedCallback() {
        private void logPrivateDataAccess(String opCode, String trace) {
            Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\nStack Trace:\n$trace");
        }

        @Override
        public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.getOp(),
                    asyncNotedAppOp.getMessage());
        }
    };

    AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
    if (appOpsManager != null) {
        appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
    }
}

onAsyncNoted() メソッドと onSelfNoted() メソッドは、次のような特定の状況で呼び出されます。

  • アプリによる API 呼び出し中にデータアクセスが行われない場合は、onAsyncNoted() が呼び出されます。最も一般的な例は、アプリがリスナーを登録し、リスナーのコールバックが呼び出されるたびにデータアクセスが行われる場合です。

    onAsyncNoted() に渡される AsyncNotedOp 引数には getMessage() というメソッドが含まれます。このメソッドは、データアクセスに関する詳細情報を提供します。位置情報に関するコールバックの場合、メッセージにはリスナーの system-identity-hash が含まれます。

  • onSelfNoted() が呼び出されるのはごくまれな場合で、アプリが自身の UID を noteOp() に渡したときです。

アトリビューション タグを使ってデータアクセスを監査する

写真を撮影する、写真を連絡先と共有するなど、主要な用途が 1 つではないアプリもあります。多目的アプリを開発する場合、データアクセスを監査するにあたって、アプリの各部分に「アトリビューション タグ」を適用できます。onNoted() の呼び出しに渡されるオブジェクト内に attributionTag コンテキストが返されます。これにより、コードの論理部分までさかのぼってデータアクセスを追跡することが簡単になります。

アプリでアトリビューション タグを定義するには、次のセクションに示す手順を完了します。

マニフェストでアトリビューション タグを宣言する

アプリが Android 12(API レベル 31)以上をターゲットとしている場合、次のコード スニペットに示す形式を使用して、アプリのマニフェスト ファイルでアトリビューション タグを宣言する必要があります。アプリのマニフェスト ファイルで宣言していないアトリビューション タグを使用しようとすると、システムが null タグを作成し、Logcat にメッセージを記録します。

<manifest ...>
    <!-- The value of "android:tag" must be a literal string, and the
         value of "android:label" must be a resource. The value of
         "android:label" is user-readable. -->
    <attribution android:tag="sharePhotos"
                 android:label="@string/share_photos_attribution_label" />
    ...
</manifest>

アトリビューション タグを作成する

位置情報のリクエストや、ユーザーの連絡先リストへのアクセスのように、データにアクセスするアクティビティの onCreate() メソッド内で、createAttributionContext() を呼び出して、アプリの構成部分に関連付けるアトリビューション タグを渡します。

次のコード スニペットでは、アプリにおける写真の位置情報の共有部分にアトリビューション タグを作成する方法を示します。

Kotlin

class SharePhotoLocationActivity : AppCompatActivity() {
    lateinit var attributionContext: Context

    override fun onCreate(savedInstanceState: Bundle?) {
        attributionContext = createAttributionContext("sharePhotos")
    }

    fun getLocation() {
        val locationManager = attributionContext.getSystemService(
                LocationManager::class.java) as LocationManager
        // Use "locationManager" to access device location information.
    }
}

Java

public class SharePhotoLocationActivity extends AppCompatActivity {
    private Context attributionContext;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState,
            @Nullable PersistableBundle persistentState) {
        attributionContext = createAttributionContext("sharePhotos");
    }

    public void getLocation() {
        LocationManager locationManager =
                attributionContext.getSystemService(LocationManager.class);
        if (locationManager != null) {
            // Use "locationManager" to access device location information.
        }
    }
}

アクセスログにアトリビューション タグを含める

AppOpsManager.OnOpNotedCallback コールバックを更新して、定義したアトリビューション タグの名前がアプリのログに含まれるようにします。

次のコード スニペットでは、アトリビューション タグをログに記録するように更新したロジックを示します。

Kotlin

val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
    private fun logPrivateDataAccess(
            opCode: String, attributionTag: String, trace: String) {
        Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\n " +
                    "Attribution Tag:$attributionTag\nStack Trace:\n$trace")
    }

    override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
        logPrivateDataAccess(syncNotedAppOp.op,
                syncNotedAppOp.attributionTag,
                Throwable().stackTrace.toString())
    }

    override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
        logPrivateDataAccess(syncNotedAppOp.op,
                syncNotedAppOp.attributionTag,
                Throwable().stackTrace.toString())
    }

    override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
        logPrivateDataAccess(asyncNotedAppOp.op,
                asyncNotedAppOp.attributionTag,
                asyncNotedAppOp.message)
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState,
        @Nullable PersistableBundle persistentState) {
    AppOpsManager.OnOpNotedCallback appOpsCallback =
            new AppOpsManager.OnOpNotedCallback() {
        private void logPrivateDataAccess(String opCode,
                String attributionTag, String trace) {
            Log.i("MY_APP_TAG", "Private data accessed. " +
                    "Operation: $opCode\n " +
                    "Attribution Tag:$attributionTag\nStack Trace:\n$trace");
        }

        @Override
        public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    syncNotedAppOp.getAttributionTag(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    syncNotedAppOp.getAttributionTag(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.getOp(),
                    asyncNotedAppOp.getAttributionTag(),
                    asyncNotedAppOp.getMessage());
        }
    };

    AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
    if (appOpsManager != null) {
        appOpsManager.setNotedAppOpsCollector(appOpsCollector);
    }
}