데이터 액세스 분석

데이터 액세스 분석을 실행하여 앱과 앱의 종속 항목이 사용자의 비공개 데이터에 액세스하는 방법에 관한 유용한 정보를 얻을 수 있습니다. Android 11(API 수준 30) 이상을 실행하는 기기에서 사용할 수 있는 이 프로세스를 통해 잠재적으로 예상치 못한 데이터 액세스를 더 잘 식별할 수 있습니다. 앱은 다음 이벤트 중 하나가 발생할 때마다 작업을 실행할 AppOpsManager.OnOpNotedCallback 인스턴스를 등록할 수 있습니다.

  • 앱의 코드가 비공개 데이터에 액세스합니다. 앱의 어떤 논리 부분에서 이벤트를 호출했는지 확인하려면 속성 태그별 데이터 액세스를 분석하면 됩니다.
  • 종속 라이브러리 또는 SDK의 코드가 비공개 데이터에 액세스합니다.

데이터 액세스 분석은 데이터 요청이 발생한 스레드에서 호출됩니다. 즉, 앱의 서드 파티 SDK 또는 라이브러리가 비공개 데이터에 액세스하는 API를 호출하는 경우 데이터 액세스 분석에서 OnOpNotedCallback가 이 호출에 관한 정보를 검토할 수 있습니다. 일반적으로 이 콜백 객체는 앱의 현재 상태(예: 현재 스레드의 스택 트레이스)를 확인하여 호출이 앱 또는 SDK에서 발생했는지 알 수 있습니다.

데이터 액세스 로그

AppOpsManager.OnOpNotedCallback 인스턴스를 사용하여 데이터 액세스 분석을 실행하려면 데이터 액세스를 분석하려는 구성요소(예: 활동의 onCreate() 메서드 또는 애플리케이션의 onCreate() 내에서)에서 콜백 로직을 구현하세요.

다음 코드 스니펫은 단일 활동 내에서 데이터 액세스를 분석하기 위한 AppOpsManager.OnOpNotedCallback을 정의합니다.

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)
}

자바

@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() 메서드는 다음과 같은 특정 상황에서 호출됩니다.

  • onAsyncNoted()는 앱의 API 호출 중에 데이터 액세스가 발생하지 않으면 호출됩니다. 가장 일반적인 예는 앱이 리스너를 등록하고 리스너의 콜백이 호출될 때마다 데이터 액세스가 발생하는 경우입니다.

    onAsyncNoted()에 전달되는 AsyncNotedOp 인수에는 getMessage()라는 메서드가 포함되어 있습니다. 이 메서드는 데이터 액세스에 관한 자세한 정보를 제공합니다. 위치 콜백의 경우 메시지에는 리스너의 system-identity-hash가 포함됩니다.

  • onSelfNoted()는 앱이 자체 UID를 noteOp()에 전달하는 매우 드문 경우에 호출됩니다.

속성 태그별 데이터 액세스 분석

앱에는 사용자가 사진을 촬영하고 이 사진을 연락처와 공유하는 등 몇 가지 주요 사용 사례가 있을 수 있습니다. 이러한 다목적 앱을 개발하는 경우 데이터 액세스를 분석할 때 앱의 각 부분에 속성 태그를 적용할 수 있습니다. attributionTag 컨텍스트는 onNoted() 호출에 전달되는 객체에 다시 반환됩니다. 이를 통해 코드의 논리 부분에 관한 데이터 액세스를 더 쉽게 추적할 수 있습니다.

앱에서 속성 태그를 정의하려면 다음 섹션의 단계를 완료하세요.

매니페스트에 저작자 표시 태그 선언

앱이 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.
    }
}

자바

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);
    }
}