Activity Recognition Transition API Codelab

1. 簡介

我們隨身攜帶手機,但到目前為止,應用程式很難根據使用者不斷變化的環境和活動調整使用體驗。

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

Activity Recognition Transition API 可解決這些問題,因為它提供簡單的 API,可為您處理所有作業,並只告知您真正在意的資訊:使用者活動何時發生變化。應用程式只需訂閱您感興趣的活動轉換事件,API 就會通知您變更

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

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

必要條件

熟悉 Android 開發作業,並對回呼有一定程度的瞭解。

課程內容

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

軟硬體需求

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

2. 開始使用

複製範例專案存放區

為了方便您盡快上手,我們準備了新手範例專案。如果您已安裝 Git,只要執行下列指令即可。(如要檢查 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)。每個資料夾圖示都稱為「模組」。

請注意,由於 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 位置和活動辨識 API 的依附元件,並在應用程式資訊清單中指定 com.google.android.gms.permission.ACTIVITY_RECOGNITION 權限。

  1. 在 build.gradle 檔案中,搜尋 「TODO:查看活動辨識所需的遊戲服務程式庫」。這個步驟 (步驟 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 類別會顯示使用者應核准活動辨識權限的原因 (最佳做法)。使用者可以點選「No Thanks」按鈕或「Continue」按鈕 (會觸發上述程式碼)。

如要進一步瞭解詳情,歡迎查看檔案。

5. 註冊/取消註冊活動轉換的接收器

在設定活動辨識程式碼之前,我們想確保活動可以處理系統提出的轉換動作。

為轉場效果建立 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. 設定活動轉換和要求更新

如要開始接收活動轉換更新,您必須導入以下程式碼:

建立要遵循的 ActivitiyTransitions 清單

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

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

base 模組中,搜尋先前建立的 BroadcastReceiver 的 onReceive() 中的 MainActivity.java 中的「TODO: 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. 查看程式碼

您已建構簡單的應用程式,可追蹤活動轉場效果,並在畫面上列出這些轉場效果。

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