Activity Recognition Transition API Codelab

1. 소개

휴대전화를 어디에나 휴대하지만 지금까지 앱이 사용자의 지속적으로 변화하는 환경과 활동에 맞게 환경을 조정하기란 쉽지 않았습니다.

이전에는 이를 위해 개발자가 다양한 신호 (위치, 센서 등)를 결합하여 걷기나 운전과 같은 활동이 시작 또는 종료된 시점을 판단하는 데 소중한 엔지니어링 시간을 할애했습니다. 더 나쁜 점은, 앱이 사용자 활동의 변화를 독립적이고 연속적으로 체크할 경우 배터리 수명이 줄어든다는 것입니다.

Activity Recognition Transition API는 모든 처리를 대신하고 실제로 중요한 사항인 사용자 활동이 변경된 시점만 알려주는 간단한 API를 제공하여 이러한 문제를 해결합니다. 앱은 관심 있는 활동의 전환을 구독하기만 하면 API가 변경사항을 알립니다.

예를 들어 메시지 앱은 '사용자가 차량에 탑승하거나 내릴 때 알려 줘'라고 요청하여 사용자의 상태를 '사용 중'으로 설정할 수 있습니다. 마찬가지로 주차 감지 앱은 '사용자가 차량에서 나와 걸어갈 때 알려 줘'라고 요청하여 사용자의 주차 위치를 저장할 수 있습니다.

이 Codelab에서는 Activity Recognition Transition API를 사용하여 사용자가 걷기나 달리기와 같은 활동을 시작/중지하는 시점을 결정하는 방법을 알아봅니다.

기본 요건

Android 개발에 대한 지식과 콜백에 대한 지식

학습할 내용

  • 활동 전환 등록
  • 이러한 이벤트 처리
  • 더 이상 필요하지 않은 경우 활동 전환 등록 취소

필요한 항목

  • Android 스튜디오 Bumblebee
  • Android 기기 또는 에뮬레이터

2. 시작하기

시작 프로젝트 저장소 클론

Google에서 준비한 시작 프로젝트를 사용하면 신속하게 빌드할 수 있습니다. git을 설치한 경우 아래 명령어를 실행하면 됩니다. 터미널/명령줄에 git --version을 입력하여 올바르게 실행되는지 확인할 수 있습니다.

 git clone https://github.com/android/codelab-activity_transitionapi

Git이 없는 경우 프로젝트를 ZIP 파일로 가져올 수 있습니다.

프로젝트 가져오기

Android 스튜디오를 시작하고 시작 화면에서 'Open an existing Android Studio project'를 선택하고 프로젝트 디렉터리를 엽니다.

프로젝트가 로드된 후 Git이 일부 로컬 변경사항을 추적하지 않는다는 알림이 표시될 수도 있습니다. 이 경우 오른쪽 상단에서 '무시' 또는 'X'를 클릭하면 됩니다. 변경사항이 Git 저장소로 다시 푸시되지 않습니다.

Android 뷰에서는 프로젝트 창의 왼쪽 상단에 아래와 같은 이미지가 표시됩니다. Project 뷰에서는 동일한 내용을 보려면 프로젝트를 펼쳐야 합니다.

d2363db913d8e5ad.png

폴더 아이콘 두 개 (basecomplete)가 있습니다. 이들을 각각 '모듈'이라고 합니다.

Android 스튜디오에서 처음으로 프로젝트를 백그라운드에서 컴파일할 때는 몇 초 정도 걸릴 수 있습니다. 그동안 Android 스튜디오 하단의 상태 표시줄에 스피너가 표시됩니다.

c9f23d5336be3cfe.png

이 작업이 끝날 때까지 기다린 다음에 코드를 변경하는 것이 좋습니다. 그러면 Android 스튜디오에서 필요한 모든 구성요소를 가져올 수 있습니다.

그 외에 'Reload for language changes to take effect?'라는 메시지나 이와 유사한 메시지가 표시되면 'Yes'를 선택합니다.

시작 프로젝트 이해

좋습니다. 이제 설정이 다 되었으며 활동 감지를 추가할 준비가 되었습니다. 이 Codelab의 시작 지점인 base 모듈을 사용하겠습니다. 다시 말해서 각 단계의 코드를 base에 추가해 보겠습니다.

complete 모듈은 작업을 확인하거나 문제가 발생했을 때 참고하는 용도로 사용할 수 있습니다.

주요 구성요소 개요:

  • MainActivity: 활동 감지에 필요한 모든 코드를 포함합니다.

에뮬레이터 설정

Android Emulator를 설정하는 데 도움이 필요한 경우 앱 실행 도움말을 참고하세요.

시작 프로젝트 실행

앱을 실행해 보겠습니다.

  • Android 기기를 컴퓨터에 연결하거나 에뮬레이터를 시작합니다.
  • 툴바의 드롭다운 선택기에서 base 구성을 선택하고 옆에 있는 녹색 삼각형 (실행) 버튼을 클릭합니다.

a640a291ffaf62ad.png

  • 아래와 같이 애플리케이션이 표시됩니다.

f58d4bb92ee77f41.png

  • 이제 앱은 메시지를 출력하는 것 외에는 아무것도 하지 않습니다. 이제 활동 감지를 추가하겠습니다.

요약

이 단계에서 알아본 내용은 다음과 같습니다.

  • Codelab의 일반적인 설정입니다.
  • 앱의 기본사항
  • 앱을 배포하는 방법

3. 라이브러리 검토 및 매니페스트에 권한 추가

앱에서 Transition API를 사용하려면 Google Location and Activity Recognition API의 종속 항목을 선언하고 앱 매니페스트에서 com.google.android.gms.permission.ACTIVITY_RECOGNITION 권한을 지정해야 합니다.

  1. build.gradle 파일에서 TODO: Review play services library required for activity recognition을 검색합니다. 이 단계 (1단계)에서는 별도의 조치를 취하지 않아도 되며 선언된 필수 종속 항목을 검토하기만 하면 됩니다. 다음과 같이 나타납니다.
    // TODO: Review play services library required for activity recognition.
    implementation 'com.google.android.gms:play-services-location:19.0.1'
  1. base 모듈의 AndroidManifest.xml에서 TODO: Add both activity recognition permissions to the manifest를 검색하고 아래 코드를 <manifest> 요소에 추가합니다.
<!-- TODO: Add both activity recognition permissions to the manifest. -->
<!-- Required for 28 and below. -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

이제 코드가 다음과 비슷하게 표시됩니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.example.myapp">
<!-- TODO: Add both activity recognition permissions to the manifest. -->
<!-- Required for 28 and below. -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

  ...
</manifest>

주석에서 볼 수 있듯이 Android 10의 두 번째 권한을 추가해야 합니다. 이는 API 버전 29에서 추가된 런타임 권한에 필요합니다.

이제 완료됐습니다. 이제 앱에서 활동 감지를 지원할 수 있습니다. 이를 사용하려면 코드를 추가하기만 하면 됩니다.

앱 실행

Android 스튜디오에서 앱을 실행합니다. 정확히 동일하게 표시됩니다. 실제로는 아직 전환 추적 코드를 추가하지 않았습니다. 이 코드는 다음 섹션에서 나옵니다.

4. Android에서 런타임 권한 확인/요청

API 버전 28 이하의 권한은 다루지만 API 버전 29 이상에서는 런타임 권한을 지원해야 합니다.

  • MainActivity.java에서 사용자가 Android 10 (29) 이상을 사용 중인지 확인하고 사용 중인 경우 활동 감지 권한을 확인합니다.
  • 권한이 부여되지 않으면 사용자는 앱에 권한이 필요한 이유가 설명되어 있으며 권한을 승인할 수 있는 스플래시 화면 (PermissionRationalActivity.java)으로 이동합니다.

Android 버전을 확인하는 코드 검토

base 모듈의 MainActivity.java에서 TODO: Review check for devices with Android 10 (29+)를 검색합니다. 다음 코드 스니펫이 표시됩니다.

이 섹션에는 특별한 작업이 없습니다.

// TODO: Review check for devices with Android 10 (29+).
private boolean runningQOrLater =
    android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;

앞서 언급한 대로 Android 10 이상에서는 런타임 권한 android.permission.ACTIVITY_RECOGNITION에 대한 승인이 필요합니다. 이 간단한 검사를 사용하여 런타임 권한을 확인해야 하는지 여부를 결정합니다.

필요한 경우 활동 감지의 런타임 권한 확인 검토

base 모듈의 MainActivity.java에서 TODO: Review permission check for 29+를 검색합니다. 다음 코드 스니펫이 표시됩니다.

이 섹션에는 특별한 작업이 없습니다.

// TODO: Review permission check for 29+.
if (runningQOrLater) {

   return PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
           this,
           Manifest.permission.ACTIVITY_RECOGNITION
   );
} else {
   return true;
}

이전 단계에서 만든 변수를 사용하여 런타임 권한을 확인해야 하는지 확인합니다.

Q 이상에서는 런타임 권한의 결과를 확인하고 반환합니다. 이 메서드는 권한을 요청해야 하는지 여부를 한 번의 간단한 호출로 개발자에게 알리는 더 큰 메서드인 activityRecognitionPermissionApproved()의 일부입니다.

런타임 권한 요청 및 활동 감지 전환 사용 설정/사용 중지

base 모듈의 MainActivity.java에서 TODO: Enable/Disable activity tracking and ask for permissions if needed를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Enable/Disable activity tracking and ask for permissions if needed.
if (activityRecognitionPermissionApproved()) {

   if (activityTrackingEnabled) {
      disableActivityTransitions();

   } else {
      enableActivityTransitions();
   }

} else {  
   // Request permission and start activity for result. If the permission is approved, we
   // want to make sure we start activity recognition tracking.
   Intent startIntent = new Intent(this, PermissionRationalActivity.class);
   startActivityForResult(startIntent, 0);

}

여기서 활동 감지가 승인되었는지 묻습니다. 활동 감지가 이미 사용 설정되어 있는 경우 사용 중지합니다. 그렇지 않으면 사용 설정됩니다.

권한이 승인되지 않은 경우, 사용자는 권한이 필요한 이유가 설명되어 있으며 권한을 부여할 수 있는 스플래시 화면 활동으로 이동됩니다.

권한 요청 코드 검토하기

base 모듈의 PermissionRationalActivity.java에서 TODO: Review permission request for activity recognition를 검색합니다. 다음 코드 스니펫이 표시됩니다.

이 섹션에는 특별한 작업이 없습니다.

// TODO: Review permission request for activity recognition.
ActivityCompat.requestPermissions(
             this,
             new String[]{Manifest.permission.ACTIVITY_RECOGNITION},
             PERMISSION_REQUEST_ACTIVITY_RECOGNITION)

이는 활동에서 가장 중요한 부분이며 검토해야 하는 부분입니다. 이 코드는 사용자가 요청하면 권한 요청을 트리거합니다.

그 외에도 PermissionRationalActivity.java 클래스는 사용자가 활동 감지 권한을 승인해야 하는 이유에 관한 근거를 표시합니다 (권장사항). 사용자는 아니요 버튼 또는 계속 버튼 (위 코드를 트리거함)을 클릭할 수 있습니다.

자세한 내용을 알아보려면 언제든지 파일을 검토하세요.

5. 활동 전환을 위한 수신기 등록/등록 취소

활동 감지 코드를 설정하기 전에 Activity가 시스템에서 발생한 전환 작업을 처리할 수 있는지 확인합니다.

전환을 위한 BroadcastReceiver 만들기

base 모듈의 MainActivity.java에서 TODO: Create a BroadcastReceiver to listen for activity transitions를 검색합니다. 아래 스니펫을 붙여넣습니다.

// TODO: Create a BroadcastReceiver to listen for activity transitions.
// The receiver listens for the PendingIntent above that is triggered by the system when an
// activity transition occurs.
mTransitionsReceiver = new TransitionsReceiver();

전환에 BroadcastReceiver 등록

base 모듈의 MainActivity.java에서 TODO: Register a BroadcastReceiver to listen for activity transitions를 검색합니다. onStart()에 있습니다. 아래 스니펫을 붙여넣습니다.

// TODO: Register a BroadcastReceiver to listen for activity transitions.
registerReceiver(mTransitionsReceiver, new IntentFilter(TRANSITIONS_RECEIVER_ACTION));

이제 PendingIntent를 통해 활동 전환이 발생할 때 업데이트를 받을 수 있습니다.

BroadcastReceiver 등록 취소

base 모듈의 MainActivity.java에서 Unregister activity transition receiver when user leaves the app를 검색합니다. (onStop()에 있음) 아래 스니펫을 붙여넣습니다.

// TODO: Unregister activity transition receiver when user leaves the app.
unregisterReceiver(mTransitionsReceiver);

Activity가 종료될 때 수신자를 등록 취소하는 것이 좋습니다.

6. 활동 전환 설정 및 업데이트 요청

활동 전환 업데이트를 받으려면 다음을 구현해야 합니다.

따라야 할 ActivityTransitions 목록 만들기

ActivityTransitionRequest 객체를 만들려면 추적하려는 전환을 나타내는 ActivityTransition 객체의 목록을 만들어야 합니다. ActivityTransition 객체에는 다음 데이터가 포함됩니다.

  1. DetectedActivity 클래스로 표시되는 활동 유형. Transition API는 다음과 같은 활동을 지원합니다.
  1. ActivityTransition 클래스로 표시되는 전환 유형입니다. 전환 유형은 다음과 같습니다.

base 모듈의 MainActivity.java에서 TODO: Add activity transitions to track를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Add activity transitions to track.
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.WALKING)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.WALKING)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.STILL)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
        .build());
activityTransitionList.add(new ActivityTransition.Builder()
        .setActivityType(DetectedActivity.STILL)
        .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
        .build());

이 코드는 추적하려는 전환을 이전에 비어 있던 목록에 추가합니다.

PendingIntent 만들기

앞서 언급했듯이 ActivityTransitionRequest의 변경사항에 대한 알림을 받으려면 PendingIntent가 필요하므로 ActivityTransitionRequest를 설정하기 전에 PendingIntent를 만들어야 합니다.

base 모듈의 MainActivity.java에서 TODO: Initialize PendingIntent that will be triggered when a activity transition occurs를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Initialize PendingIntent that will be triggered when a activity transition occurs.
Intent intent = new Intent(TRANSITIONS_RECEIVER_ACTION);
mActivityTransitionsPendingIntent =
        PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);

이제 ActivityTransition 중 하나가 발생할 때 트리거할 수 있는 PendingIntent가 있습니다.

ActivityTransitionRequest 만들기 및 업데이트 요청

ActivityTransitions 목록을 ActivityTransitionRequest 클래스에 전달하여 ActivityTransitionRequest 객체를 만들 수 있습니다.

base 모듈의 MainActivity.java에서 Create request and listen for activity changes를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Create request and listen for activity changes.
ActivityTransitionRequest request = new ActivityTransitionRequest(activityTransitionList);

// Register for Transitions Updates.
Task<Void> task =
        ActivityRecognition.getClient(this)
                .requestActivityTransitionUpdates(request, mActivityTransitionsPendingIntent);


task.addOnSuccessListener(
        new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void result) {
                activityTrackingEnabled = true;
                printToScreen("Transitions Api was successfully registered.");

            }
        });
task.addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions Api could NOT be registered: " + e);
                Log.e(TAG, "Transitions Api could NOT be registered: " + e);

            }
        });

코드를 검토해 보겠습니다. 먼저 활동 전환 목록에서 ActivityTransitionRequest를 만듭니다.

ActivityTransitionRequest request = new ActivityTransitionRequest(activityTransitionList);

그런 다음 ActivityTransitionRequest 인스턴스와 마지막 단계에서 만든 PendingIntent 객체를 requestActivityTransitionUpdates() 메서드에 전달하여 활동 전환 업데이트를 등록합니다. requestActivityTransitionUpdates() 메서드는 다음 코드 블록과 같이 성공 또는 실패를 확인할 수 있는 Task 객체를 반환합니다.

// Register for Transitions Updates.
Task<Void> task =
        ActivityRecognition.getClient(this)
                .requestActivityTransitionUpdates(request, mActivityTransitionsPendingIntent);


task.addOnSuccessListener(
        new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void result) {
                activityTrackingEnabled = true;
                printToScreen("Transitions Api was successfully registered.");

            }
        });
task.addOnFailureListener(
        new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions Api could NOT be registered: " + e);
                Log.e(TAG, "Transitions Api could NOT be registered: " + e);

            }
        });

활동 전환 업데이트를 성공적으로 등록하면 앱은 등록된 PendingIntent에서 알림을 받습니다. 또한 사용자가 버튼을 다시 클릭할 때 사용 중지/사용 설정 여부를 알 수 있도록 활동 추적이 사용 설정된 변수를 설정합니다.

앱 종료 시 업데이트 삭제

앱이 닫힐 때 전환 업데이트를 삭제하는 것이 중요합니다.

base 모듈의 MainActivity.java에서 Stop listening for activity changes를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Stop listening for activity changes.
ActivityRecognition.getClient(this).removeActivityTransitionUpdates(mActivityTransitionsPendingIntent)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                activityTrackingEnabled = false;
                printToScreen("Transitions successfully unregistered.");
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                printToScreen("Transitions could not be unregistered: " + e);
                Log.e(TAG,"Transitions could not be unregistered: " + e);
            }
        });

이제 앱이 종료될 때 위의 코드가 포함된 메서드를 호출해야 합니다.

base 모듈의 onPause()에 있는 MainActivity.java에서 TODO: Disable activity transitions when user leaves the app를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Disable activity transitions when user leaves the app.
if (activityTrackingEnabled) {
    disableActivityTransitions();
}

이제 활동 전환의 변경사항을 추적할 준비가 되었습니다. 이제 업데이트를 처리하기만 하면 됩니다.

7. 이벤트 처리

요청된 활동 전환이 발생하면 앱은 Intent 콜백을 수신합니다. ActivityTransitionResult 객체는 ActivityTransitionEvent 객체의 목록이 포함된 인텐트에서 추출할 수 있습니다. 이벤트는 시간 순서대로 정렬됩니다. 예를 들어 앱이 ACTIVITY_TRANSITION_ENTERACTIVITY_TRANSITION_EXIT 전환에서 IN_VEHICLE 활동 유형을 요청하는 경우, 사용자가 운전을 시작하면 ActivityTransitionEvent 객체를 수신하고 사용자가 다른 활동으로 전환하면 다른 객체를 수신합니다.

이러한 이벤트를 처리하기 위해 코드를 추가해 보겠습니다.

base 모듈의 이전에 만든 BroadcastReceiver의 onReceive()에서 MainActivity.javaTODO: Extract activity transition information from listener를 검색합니다. 주석 뒤에 아래 코드를 추가합니다.

// TODO: Extract activity transition information from listener.
if (ActivityTransitionResult.hasResult(intent)) {

    ActivityTransitionResult result = ActivityTransitionResult.extractResult(intent);

    for (ActivityTransitionEvent event : result.getTransitionEvents()) {

        String info = "Transition: " + toActivityString(event.getActivityType()) +
                " (" + toTransitionType(event.getTransitionType()) + ")" + "   " +
                new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date());

        printToScreen(info);
    }
}

이렇게 하면 정보가 String로 변환되어 화면에 출력됩니다.

모두 끝났습니다. 앱을 실행해 봅니다.

중요사항: 에뮬레이터에서는 활동 변경사항을 재현하기 어렵기 때문에 실제 기기를 사용하는 것이 좋습니다.

활동 변경사항을 추적할 수 있어야 합니다.

최상의 결과를 얻으려면 실제 기기에 앱을 설치하고 걸어보세요. :)

8. 코드 검토

Activity 전환을 추적하고 화면에 나열하는 간단한 앱을 빌드했습니다.

원하는 경우 코드를 전체적으로 꼼꼼히 살펴보고 지금까지 한 작업을 검토해 코드가 함께 어떻게 작동하는지 더 확실히 파악해 보세요.