Activity Recognition Transition API Codelab

1. 簡介

我們幾乎到哪都隨身攜帶手機,但應用程式一直難以根據使用者不斷變化的環境和活動調整體驗。

過去,開發人員必須花費寶貴的工程時間,結合各種信號 (位置、感應器等),才能判斷步行或開車等活動的開始或結束時間。更糟的是,如果應用程式持續獨立檢查使用者活動的變化,電池續航力就會受到影響。

Activity Recognition Transition API 提供簡單的 API,可為您處理所有作業,並只回報您真正關心的資訊:使用者活動的變化。應用程式只要訂閱您感興趣的活動轉換事件,API 就會通知您相關變更

舉例來說,訊息應用程式可以要求「在使用者進入或離開車輛時通知我」,以便將使用者的狀態設為忙碌。同樣地,停車偵測應用程式可以詢問「使用者下車並開始步行時通知我」,以便儲存使用者的停車位置。

在本程式碼研究室中,您將瞭解如何使用 Activity Recognition Transition API,判斷使用者何時開始/停止步行或跑步等活動。

必要條件

熟悉 Android 開發,並對回呼有一定瞭解。

課程內容

  • 註冊活動轉換
  • 處理這些事件
  • 不再需要活動轉換時,取消註冊

軟硬體需求

  • Android Studio Bumblebee
  • Android 裝置或模擬器

2. 開始使用

複製範例專案存放區

為了方便您盡快上手,我們準備了新手範例專案。如果您已安裝 Git,只要執行下列指令即可。(如要檢查,請在終端機 / 指令列中輸入 git --version,然後確認是否正常運作)。

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

如果您沒有 Git,可以將專案下載為 ZIP 檔案:

匯入專案

啟動 Android Studio,然後在歡迎畫面中選取「Open an existing Android Studio project」,然後開啟專案目錄。

專案載入後,系統也可能會顯示快訊,通知您 Git 不會追蹤所有本機變更,您可以按一下「Ignore」或右上方的「X」圖示 (系統不會將任何變更推送回 Git 存放區。)

如果您在「Android」檢視畫面中,專案視窗的左上角會顯示如下圖所示的項目 (在「Project」檢視畫面中,您必須展開專案才能看到這些項目)。

d2363db913d8e5ad.png

共有兩個資料夾圖示 (basecomplete)。每個資料夾圖示都稱為「module」(模組)。

請注意,第一次開啟專案時,Android Studio 可能需要花幾秒的時間在背景中編譯專案。此時,Android Studio 底部的狀態列會顯示旋轉圖示:

c9f23d5336be3cfe.png

建議您等到此動作完成後再變更程式碼。這使 Android Studio 提取所有必要的元件。

此外,如果畫面顯示「Reload for language changes to take effect?」的提示或類似內容,請選取「Yes」。

瞭解範例專案

一切準備就緒,可以新增活動辨識功能了。我們將使用 base 模組,這是本程式碼研究室的起點。換句話說,您必須在每個步驟中新增程式碼至 base

complete 模組可用於檢查你的作品,在發生問題時也可以參考。

主要元件總覽:

  • MainActivity:包含活動辨識所需的所有程式碼。

模擬器設定

如需設定 Android 模擬器的相關說明,請參閱「執行應用程式」一文。

執行範例專案

執行應用程式。

  • 將 Android 裝置連接到電腦,或啟動模擬器。
  • 在工具列中,從下拉式選取器中選取 base 設定,然後按一下旁邊的綠色三角形 (執行) 按鈕:

a640a291ffaf62ad.png

  • 您應該會看到下列應用程式:

f58d4bb92ee77f41.png

  • 這個應用程式目前只會列印訊息,不會執行任何其他動作。現在要新增活動辨識功能。

摘要

您在此步驟中瞭解到:

  • 程式碼研究室的一般設定。
  • 應用程式基本概念。
  • 如何部署應用程式。

3. 查看程式庫及在資訊清單中新增權限

如要在應用程式中使用 Transition API,必須宣告 Google Location 及 Activity Recognition API 的依附元件,並在應用程式資訊清單中指定 com.google.android.gms.permission.ACTIVITY_RECOGNITION 權限。

  1. 在 build.gradle 檔案中,搜尋 「TODO: Review play services library required for activity recognition」(TODO:查看活動辨識所需的 Play 服務程式庫)。這個步驟 (步驟 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 Studio 執行應用程式。看起來應該完全相同。我們尚未實際新增任何用於追蹤轉場效果的程式碼,不過會在下一個章節提供。

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. 設定活動轉換和要求更新

如要開始接收活動轉換的狀態更新,請導入下列程式碼:

建立要追蹤的 ActivityTransition 清單

如要建立 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);

現在我們有了 PendingIntent,可以在發生 ActivityTransition 時觸發。

建立 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 模組中,搜尋 MainActivity.javaonPause() 的「TODO: Disable activity transitions when user leaves the app」。在註解後方新增以下程式碼。

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

這樣就完成活動轉換變更的追蹤作業。現在只要處理更新即可。

7. 處理事件

當要求的活動發生轉換,應用程式會收到 Intent 回呼。您可以從 Intent 擷取 ActivityTransitionResult 物件,其中包含 ActivityTransitionEvent 物件的清單。事件會依時間先後順序排列,例如,如果應用程式要求 IN_VEHICLE 活動類型 (位於 ACTIVITY_TRANSITION_ENTERACTIVITY_TRANSITION_EXIT 轉換),則當使用者開始開車時,就會收到一個 ActivityTransitionEvent 物件,當使用者轉換至其他活動時,則會收到另一個。

讓我們新增程式碼來處理這些事件。

base 模組中,搜尋 MainActivity.java 中「TODO: Extract activity transition information from listener」,並在稍早建立的 BroadcastReceiver 的 onReceive() 中,在註解後方新增以下程式碼。

// 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. 檢查程式碼

您已建構簡單的應用程式,可追蹤活動轉換並列在畫面上。

歡迎您隨時詳閱完整程式碼,瞭解自己完成的部分,並進一步瞭解程式碼如何搭配運作。