Simulating navigation state events

Change-Id: I37312a1f82b9a648778d2f0cbd9f80baa796a562
But: 79884417
Test: Manually on device
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index 8239f3c..e6f457e 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -41,17 +41,29 @@
 LOCAL_DEX_PREOPT := false
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
+    car-service-lib-for-test \
+    car-apps-common \
     androidx.car_car \
-    car-service-lib-for-test
+    androidx.car_car-cluster
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android.hidl.base-V1.0-java \
     android.hardware.automotive.vehicle-V2.0-java \
     vehicle-hal-support-lib \
-    com.android.car.keventreader-client
+    com.android.car.keventreader-client \
+    kitchensink-gson
 
 LOCAL_JAVA_LIBRARIES += android.car
 
 include $(BUILD_PACKAGE)
 
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    kitchensink-gson:libs/gson-2.1.jar
+
+include $(BUILD_MULTI_PREBUILT)
+
+include $(CLEAR_VARS)
+
 endif #TARGET_BUILD_PDK
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
new file mode 100644
index 0000000..09396a0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
new file mode 100644
index 0000000..83c5c99
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
new file mode 100644
index 0000000..2aa5cd0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
@@ -0,0 +1,55 @@
+[
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 200,
+          "mDisplayValue": "0.2",
+          "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "DEPART" ] }
+        }
+      }
+    ],
+    "mDestinations": [
+      {
+        "mTitle": "Home",
+        "mAddress": "123 Main st",
+        "mDistance": {
+          "mMeters": 2000,
+          "mDisplayValue": "2",
+          "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+        }
+      }
+    ]
+  },
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 91,
+          "mDisplayValue": "300",
+          "mDisplayUnit": { "mValues": [ "FEET" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "TURN_NORMAL_LEFT" ] }
+        }
+      }
+    ]
+  },
+  {
+    "mSteps": [
+      {
+        "mDistance": {
+          "mMeters": 3218,
+          "mDisplayValue": "2",
+          "mDisplayUnit": { "mValues": [ "MILES" ] }
+        },
+        "mManeuver": {
+          "mType": { "mValues": [ "TURN_NORMAL_RIGHT" ] }
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index 4f5fc5b..ad4b93a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -33,22 +33,38 @@
 import android.view.ViewGroup;
 import android.widget.Toast;
 
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.cluster.navigation.NavigationState;
 import androidx.fragment.app.Fragment;
 
 import com.google.android.car.kitchensink.R;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.stream.Collectors;
 
 /**
  * Contains functions to test instrument cluster API.
  */
 public class InstrumentClusterFragment extends Fragment {
-    private static final String TAG = InstrumentClusterFragment.class.getSimpleName();
+    private static final String TAG = "Cluster.KitchenSink";
 
     private static final int DISPLAY_IN_CLUSTER_PERMISSION_REQUEST = 1;
 
     private CarNavigationStatusManager mCarNavigationStatusManager;
     private CarAppFocusManager mCarAppFocusManager;
     private Car mCarApi;
+    private Timer mTimer;
+    private NavigationState[] mNavStateData;
 
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
             @Override
@@ -81,6 +97,37 @@
         mCarApi.connect();
     }
 
+    /**
+     * Loads sample navigation data from the "nav_state_data.json" file.
+     */
+    @NonNull
+    private NavigationState[] getNavStateData() {
+        try {
+            Gson gson = new GsonBuilder().create();
+            String navStateData = getRawResourceAsString(R.raw.nav_state_data);
+            NavigationState[] navigationState = gson.fromJson(navStateData,
+                    NavigationState[].class);
+            return navigationState;
+        } catch (IOException ex) {
+            Log.e(TAG, "Unable to read navigation state data", ex);
+            return new NavigationState[0];
+        }
+    }
+
+    /**
+     * Loads a raw resource as a single string.
+     */
+    @NonNull
+    private String getRawResourceAsString(@IdRes int resId) throws IOException {
+        InputStream inputStream = getResources().openRawResource(resId);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+        StringBuilder builder = new StringBuilder();
+        for (String line = null; (line = reader.readLine()) != null; ) {
+            builder.append(line).append("\n");
+        }
+        return builder.toString();
+    }
+
     @Nullable
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -88,7 +135,7 @@
         View view = inflater.inflate(R.layout.instrument_cluster, container, false);
 
         view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
-        view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> sendTurn());
+        view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> toogleSendTurn());
         view.findViewById(R.id.cluster_start_activity).setOnClickListener(v -> startNavActivity());
 
         return view;
@@ -97,7 +144,6 @@
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         initCarApi();
-
         super.onCreate(savedInstanceState);
     }
 
@@ -126,12 +172,46 @@
         }
     }
 
-    private void sendTurn() {
-        // TODO(deanh): Make this actually meaningful.
-        Bundle bundle = new Bundle();
-        bundle.putString("someName", "someValue time=" + System.currentTimeMillis());
+    /**
+     * Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
+     */
+    private void toogleSendTurn() {
+        // If we haven't yet load the sample navigation state data, do so.
+        if (mNavStateData == null) {
+            mNavStateData = getNavStateData();
+            Log.i(TAG, "Loaded: " + Arrays.asList(mNavStateData)
+                    .stream()
+                    .map(n -> n.toString())
+                    .collect(Collectors.joining(", ")));
+        }
+
+        // Toggle a timer to send update periodically.
+        if (mTimer == null) {
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                private int mPos;
+
+                @Override
+                public void run() {
+                    sendTurn(mNavStateData[mPos]);
+                    mPos = (mPos + 1) % mNavStateData.length;
+                }
+            }, 0, 1000);
+        } else {
+            mTimer.cancel();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * Sends one update of the navigation state through the {@link CarNavigationStatusManager}
+     */
+    private void sendTurn(@NonNull NavigationState state) {
         try {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable("navstate", state.toParcelable());
             mCarNavigationStatusManager.sendEvent(1, bundle);
+            Log.i(TAG, "Sending nav state: " + state);
         } catch(CarNotConnectedException e) {
             Log.e(TAG, "Failed to send turn information.", e);
         }
@@ -185,8 +265,6 @@
         } catch (CarNotConnectedException e) {
             Log.e(TAG, "Failed to get owned focus", e);
         }
-
-        // TODO(deanh): re-implement this using sendEvent()
     }
 
     @Override