Snap for 5699409 from f4ed44d3ff5f0312a496bc793bb6183812e7406a to qt-aml-release

Change-Id: I80adcaefbb619a26f777c6d75ac112f985228306
diff --git a/Android.mk b/Android.mk
index bd9c68d..ef92d7e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -35,11 +35,13 @@
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android.car.cluster.navigation
+
 LOCAL_JAVA_LIBRARIES += android.car
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     androidx.legacy_legacy-support-v4 \
     androidx-constraintlayout_constraintlayout \
-    androidx.car_car-cluster \
     car-arch-common \
     car-media-common \
     car-telephony-common \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fc9246d..7dc2bb6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,6 +49,8 @@
     <uses-permission android:name="android.car.permission.CAR_SPEED"/>
     <uses-permission android:name="android.car.permission.CAR_ENGINE_DETAILED"/>
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
     <application android:label="@string/app_name"
                  android:icon="@mipmap/ic_launcher"
                  android:directBootAware="true">
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5afdb28..bff5c60 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -103,5 +103,5 @@
     <dimen name="user_profile_title_padding_top">@*android:dimen/car_padding_3</dimen>
     <dimen name="user_profile_body_padding_top">@*android:dimen/car_padding_3</dimen>
 
-    <dimen name="large_avatar_icon_size">@dimen/car_large_avatar_size</dimen>
+    <dimen name="large_avatar_icon_size">96dp</dimen>
 </resources>
diff --git a/src/android/car/cluster/ClusterRenderingService.java b/src/android/car/cluster/ClusterRenderingService.java
index 85dacdf..6c924c4 100644
--- a/src/android/car/cluster/ClusterRenderingService.java
+++ b/src/android/car/cluster/ClusterRenderingService.java
@@ -21,8 +21,7 @@
 
 import android.app.ActivityOptions;
 import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
-import android.car.cluster.ClusterActivityState;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
 import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.car.cluster.renderer.NavigationRenderer;
 import android.car.navigation.CarNavigationInstrumentCluster;
@@ -41,8 +40,7 @@
 import android.view.InputDevice;
 import android.view.KeyEvent;
 
-import androidx.car.cluster.navigation.NavigationState;
-import androidx.versionedparcelable.ParcelUtils;
+import com.google.protobuf.InvalidProtocolBufferException;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -63,7 +61,7 @@
 
     static final int NAV_STATE_EVENT_ID = 1;
     static final String LOCAL_BINDING_ACTION = "local";
-    static final String NAV_STATE_BUNDLE_KEY = "navstate";
+    static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
 
     private List<ServiceClient> mClients = new ArrayList<>();
     private ClusterDisplayProvider mDisplayProvider;
@@ -73,7 +71,8 @@
 
     public interface ServiceClient {
         void onKeyEvent(KeyEvent keyEvent);
-        void onNavigationStateChange(NavigationState navState);
+
+        void onNavigationStateChange(NavigationStateProto navState);
     }
 
     public class LocalBinder extends Binder {
@@ -190,30 +189,34 @@
 
             @Override
             public void onEvent(int eventType, Bundle bundle) {
-                try {
-                    StringBuilder bundleSummary = new StringBuilder();
-                    if (eventType == NAV_STATE_EVENT_ID) {
-                        bundle.setClassLoader(ParcelUtils.class.getClassLoader());
-                        NavigationState navState = NavigationState
-                                .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
-                        bundleSummary.append(navState.toString());
+                StringBuilder bundleSummary = new StringBuilder();
+                if (eventType == NAV_STATE_EVENT_ID) {
+                    // Attempt to read proto byte array
+                    byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
+                    if (protoBytes != null) {
+                        try {
+                            NavigationStateProto navState = NavigationStateProto.parseFrom(
+                                    protoBytes);
+                            bundleSummary.append(navState.toString());
 
-                        // Update clients
-                        broadcastClientEvent(client -> client.onNavigationStateChange(navState));
-                    } else {
-                        for (String key : bundle.keySet()) {
-                            bundleSummary.append(key);
-                            bundleSummary.append("=");
-                            bundleSummary.append(bundle.get(key));
-                            bundleSummary.append(" ");
+                            // Update clients
+                            broadcastClientEvent(
+                                    client -> client.onNavigationStateChange(navState));
+                        } catch (InvalidProtocolBufferException e) {
+                            Log.e(TAG, "Error parsing navigation state proto", e);
                         }
+                    } else {
+                        Log.e(TAG, "Received nav state byte array is null");
                     }
-                    Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
-                } catch (Exception e) {
-                    Log.e(TAG, "Error parsing event data (" + eventType + ", " + bundle + ")", e);
-                    NavigationState navState = new NavigationState.Builder().build();
-                    broadcastClientEvent(client -> client.onNavigationStateChange(navState));
+                } else {
+                    for (String key : bundle.keySet()) {
+                        bundleSummary.append(key);
+                        bundleSummary.append("=");
+                        bundleSummary.append(bundle.get(key));
+                        bundleSummary.append(" ");
+                    }
                 }
+                Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
             }
         };
 
@@ -251,17 +254,17 @@
             scanCode = 106;
         }
         return KeyEvent.obtain(
-                    downTime,
-                    eventTime,
-                    action,
-                    keyCode,
-                    0 /* repeat */,
-                    0 /* meta state */,
-                    0 /* deviceId*/,
-                    scanCode /* scancode */,
-                    KeyEvent.FLAG_FROM_SYSTEM /* flags */,
-                    InputDevice.SOURCE_KEYBOARD,
-                    null /* characters */);
+                downTime,
+                eventTime,
+                action,
+                keyCode,
+                0 /* repeat */,
+                0 /* meta state */,
+                0 /* deviceId*/,
+                scanCode /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM /* flags */,
+                InputDevice.SOURCE_KEYBOARD,
+                null /* characters */);
     }
 
     private void execShellCommand(String[] args) {
diff --git a/src/android/car/cluster/CueView.java b/src/android/car/cluster/CueView.java
index f33834e..fa08846 100644
--- a/src/android/car/cluster/CueView.java
+++ b/src/android/car/cluster/CueView.java
@@ -25,9 +25,9 @@
 import android.util.Log;
 import android.widget.TextView;
 
-import androidx.car.cluster.navigation.ImageReference;
-import androidx.car.cluster.navigation.RichText;
-import androidx.car.cluster.navigation.RichTextElement;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.car.cluster.navigation.NavigationState.Cue;
+import android.car.cluster.navigation.NavigationState.Cue.CueElement;
 
 import java.util.Collections;
 import java.util.List;
@@ -45,7 +45,7 @@
     private String mImageSpanText;
     private CompletableFuture<?> mFuture;
     private Handler mHandler = new Handler();
-    private RichText mContent;
+    private Cue mContent;
 
     public CueView(Context context) {
         super(context);
@@ -66,41 +66,41 @@
         mImageSpanText = context.getString(R.string.span_image);
     }
 
-    public void setRichText(RichText richText, ImageResolver imageResolver) {
-        if (richText == null) {
+    public void setCue(Cue cue, ImageResolver imageResolver) {
+        if (cue == null) {
             setText(null);
             return;
         }
 
-        if (mFuture != null && !Objects.equals(richText, mContent)) {
+        if (mFuture != null && !Objects.equals(cue, mContent)) {
             mFuture.cancel(true);
         }
 
-        List<ImageReference> imageReferences = richText.getElements().stream()
-                .filter(element -> element.getImage() != null)
+        List<ImageReference> imageReferences = cue.getElementsList().stream()
+                .filter(element -> element.hasImage())
                 .map(element -> element.getImage())
                 .collect(Collectors.toList());
         mFuture = imageResolver
                 .getBitmaps(imageReferences, 0, getLineHeight())
                 .thenAccept(bitmaps -> {
-                    mHandler.post(() -> update(richText, bitmaps));
+                    mHandler.post(() -> update(cue, bitmaps));
                     mFuture = null;
                 })
                 .exceptionally(ex -> {
                     if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Unable to fetch images for cue: " + richText);
+                        Log.d(TAG, "Unable to fetch images for cue: " + cue);
                     }
-                    mHandler.post(() -> update(richText, Collections.emptyMap()));
+                    mHandler.post(() -> update(cue, Collections.emptyMap()));
                     return null;
                 });
-        mContent = richText;
+        mContent = cue;
     }
 
-    private void update(RichText richText, Map<ImageReference, Bitmap> bitmaps) {
+    private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps) {
         SpannableStringBuilder builder = new SpannableStringBuilder();
 
-        for (RichTextElement element : richText.getElements()) {
-            if (element.getImage() != null) {
+        for (CueElement element : cue.getElementsList()) {
+            if (element.hasImage()) {
                 Bitmap bitmap = bitmaps.get(element.getImage());
                 if (bitmap != null) {
                     String imageText = element.getText().isEmpty() ? mImageSpanText :
diff --git a/src/android/car/cluster/ImageResolver.java b/src/android/car/cluster/ImageResolver.java
index e425bbd..45fcf31 100644
--- a/src/android/car/cluster/ImageResolver.java
+++ b/src/android/car/cluster/ImageResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,15 +15,14 @@
  */
 package android.car.cluster;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.car.cluster.renderer.InvalidSizeException;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.net.Uri;
 import android.util.Log;
-import android.util.LruCache;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.car.cluster.navigation.ImageReference;
 
 import java.util.List;
 import java.util.Map;
@@ -32,22 +31,21 @@
 
 /**
  * Class for retrieving bitmap images from a ContentProvider
+ *
+ * @hide
  */
 public class ImageResolver {
     private static final String TAG = "Cluster.ImageResolver";
-    private static final int IMAGE_CACHE_SIZE_BYTES = 4 * 1024 * 1024; /* 4 mb */
-
     private final BitmapFetcher mFetcher;
-    private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(
-            IMAGE_CACHE_SIZE_BYTES) {
-        @Override
-        protected int sizeOf(String key, Bitmap value) {
-            return value.getByteCount();
-        }
-    };
 
+    /**
+     * Interface used for fetching bitmaps from a content resolver
+     */
     public interface BitmapFetcher {
-        Bitmap getBitmap(Uri uri);
+        /**
+         * Returns a {@link Bitmap} given a request Uri and dimensions
+         */
+        Bitmap getBitmap(Uri uri, int width, int height) throws InvalidSizeException;
     }
 
     /**
@@ -62,45 +60,43 @@
      * This image would fit inside the provided size. Either width, height or both should be greater
      * than 0.
      *
-     * @param width required width, or 0 if width is flexible based on height.
+     * @param width  required width, or 0 if width is flexible based on height.
      * @param height required height, or 0 if height is flexible based on width.
      */
     @NonNull
     public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
-                    img.getRawContentUri(), width, height));
+                    img.getContentUri(), width, height));
         }
 
         return CompletableFuture.supplyAsync(() -> {
             // Adjust the size to fit in the requested box.
-            Point adjusted = getAdjustedSize(img.getOriginalWidth(), img.getOriginalHeight(), width,
-                    height);
+            Point adjusted = getAdjustedSize(img.getAspectRatio(), width, height);
             if (adjusted == null) {
-                Log.e(TAG, "The provided image has no original size: " + img.getRawContentUri());
+                Log.e(TAG, "The provided image has no aspect ratio: " + img.getContentUri());
                 return null;
             }
-            Uri uri = img.getContentUri(adjusted.x, adjusted.y);
-            Bitmap bitmap = mCache.get(uri.toString());
-            if (bitmap == null) {
-                bitmap = mFetcher.getBitmap(uri);
-                if (bitmap == null) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Unable to fetch image: " + uri);
-                    }
-                    return null;
-                }
-                if (bitmap.getWidth() != adjusted.x || bitmap.getHeight() != adjusted.y) {
-                    bitmap = Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true);
-                }
-                mCache.put(uri.toString(), bitmap);
+
+            Uri uri = Uri.parse(img.getContentUri());
+            Bitmap bitmap = null;
+            try {
+                bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y);
+            } catch (InvalidSizeException e) {
+                Log.e(TAG, "Bitmap must have positive width and height");
             }
+            if (bitmap == null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Unable to fetch image: " + uri);
+                }
+                return null;
+            }
+
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
-                        img.getRawContentUri(), width, height));
+                        img.getContentUri(), width, height));
             }
-            return bitmap != null ? Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true)
-                    : null;
+            return bitmap;
         });
     }
 
@@ -109,7 +105,7 @@
      * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
      * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
      *
-     * @param width required width, or 0 if width is flexible based on height.
+     * @param width  required width, or 0 if width is flexible based on height.
      * @param height required height, or 0 if height is flexible based on width.
      */
     @NonNull
@@ -141,15 +137,14 @@
      * Returns an image size that exactly fits inside a requested box, maintaining an original size
      * aspect ratio.
      *
-     * @param originalWidth original width (must be != 0)
-     * @param originalHeight original height (must be != 0)
-     * @param requestedWidth required width, or 0 if width is flexible based on height.
+     * @param imageRatio      original aspect ratio (must be > 0)
+     * @param requestedWidth  required width, or 0 if width is flexible based on height.
      * @param requestedHeight required height, or 0 if height is flexible based on width.
      */
     @Nullable
-    public Point getAdjustedSize(int originalWidth, int originalHeight, int requestedWidth,
+    public Point getAdjustedSize(double imageRatio, int requestedWidth,
             int requestedHeight) {
-        if (originalWidth <= 0 || originalHeight <= 0) {
+        if (imageRatio <= 0) {
             return null;
         } else if (requestedWidth == 0 && requestedHeight == 0) {
             throw new IllegalArgumentException("At least one of width or height must be != 0");
@@ -157,12 +152,11 @@
         // If width is flexible or if both width and height are set and the original image is wider
         // than the space provided, then scale the width.
         float requiredRatio = requestedHeight > 0 ? ((float) requestedWidth) / requestedHeight : 0;
-        float imageRatio = ((float) originalWidth) / originalHeight;
         Point res = new Point(requestedWidth, requestedHeight);
         if (requestedWidth == 0 || (requestedHeight != 0 && imageRatio < requiredRatio)) {
-            res.x = (int) (((float) requestedHeight / originalHeight) * originalWidth);
+            res.x = (int) (imageRatio * requestedHeight);
         } else {
-            res.y = (int) (((float) requestedWidth / originalWidth) * originalHeight);
+            res.y = (int) (requestedWidth / imageRatio);
         }
         return res;
     }
diff --git a/src/android/car/cluster/LaneView.java b/src/android/car/cluster/LaneView.java
index 2b629c6..2a5ca58 100644
--- a/src/android/car/cluster/LaneView.java
+++ b/src/android/car/cluster/LaneView.java
@@ -29,9 +29,9 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
-import androidx.car.cluster.navigation.ImageReference;
-import androidx.car.cluster.navigation.Lane;
-import androidx.car.cluster.navigation.LaneDirection;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.car.cluster.navigation.NavigationState.Lane;
+import android.car.cluster.navigation.NavigationState.Lane.LaneDirection;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -104,7 +104,7 @@
     }
 
     private Bitmap combineBitmapFromLane(Lane lane) {
-        if (lane.getDirections().isEmpty()) {
+        if (lane.getLaneDirectionsList().isEmpty()) {
             return null;
         }
 
@@ -113,14 +113,14 @@
 
         Shift shift = getShift(lane);
 
-        for (LaneDirection laneDir : lane.getDirections()) {
-            if (!laneDir.isHighlighted()) {
+        for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
+            if (!laneDir.getIsHighlighted()) {
                 drawToCanvas(laneDir, canvas, false, shift);
             }
         }
 
-        for (LaneDirection laneDir : lane.getDirections()) {
-            if (laneDir.isHighlighted()) {
+        for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
+            if (laneDir.getIsHighlighted()) {
                 drawToCanvas(laneDir, canvas, true, shift);
             }
         }
@@ -148,7 +148,7 @@
         boolean containsLeft = false;
         boolean containsStraight = false;
 
-        for (LaneDirection laneDir : lane.getDirections()) {
+        for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
             if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_RIGHT)
                     || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_RIGHT)
                     || laneDir.getShape().equals(LaneDirection.Shape.SHARP_RIGHT)
diff --git a/src/android/car/cluster/LoggingClusterRenderingService.java b/src/android/car/cluster/LoggingClusterRenderingService.java
index e58390b..89990f9 100644
--- a/src/android/car/cluster/LoggingClusterRenderingService.java
+++ b/src/android/car/cluster/LoggingClusterRenderingService.java
@@ -23,17 +23,15 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import androidx.car.cluster.navigation.NavigationState;
-import androidx.versionedparcelable.ParcelUtils;
-
 import com.google.android.collect.Lists;
+import com.google.protobuf.InvalidProtocolBufferException;
 
 /**
  * Dummy implementation of {@link LoggingClusterRenderingService} to log all interaction.
  */
 public class LoggingClusterRenderingService extends InstrumentClusterRenderingService {
     private static final String TAG = LoggingClusterRenderingService.class.getSimpleName();
-    private static final String NAV_STATE_BUNDLE_KEY = "navstate";
+    private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
     private static final int NAV_STATE_EVENT_ID = 1;
 
     @Override
@@ -53,15 +51,27 @@
             public void onEvent(int eventType, Bundle bundle) {
                 StringBuilder bundleSummary = new StringBuilder();
                 if (eventType == NAV_STATE_EVENT_ID) {
-                    bundle.setClassLoader(ParcelUtils.class.getClassLoader());
-                    NavigationState navState = NavigationState
-                            .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
-                    bundleSummary.append(navState.toString());
+                    // Attempt to read proto byte array
+                    byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
+                    if (protoBytes != null) {
+                        try {
+                            android.car.cluster.navigation.NavigationState.NavigationStateProto
+                                    navState =
+                                    android.car.cluster.navigation.NavigationState.NavigationStateProto.parseFrom(
+                                            protoBytes);
+                            bundleSummary.append(navState.toString());
 
-                    // Sending broadcast for testing.
-                    Intent intent = new Intent("android.car.cluster.NAVIGATION_STATE_UPDATE");
-                    intent.putExtra(NAV_STATE_BUNDLE_KEY, bundle);
-                    sendBroadcastAsUser(intent, UserHandle.ALL);
+                            // Sending broadcast for testing.
+                            Intent intent = new Intent(
+                                    "android.car.cluster.NAVIGATION_STATE_UPDATE");
+                            intent.putExtra(NAV_STATE_PROTO_BUNDLE_KEY, bundle);
+                            sendBroadcastAsUser(intent, UserHandle.ALL);
+                        } catch (InvalidProtocolBufferException e) {
+                            Log.e(TAG, "Error parsing navigation state proto", e);
+                        }
+                    } else {
+                        Log.e(TAG, "Received nav state byte array is null");
+                    }
                 } else {
                     for (String key : bundle.keySet()) {
                         bundleSummary.append(key);
diff --git a/src/android/car/cluster/MainClusterActivity.java b/src/android/car/cluster/MainClusterActivity.java
index d4138f2..cf5699e 100644
--- a/src/android/car/cluster/MainClusterActivity.java
+++ b/src/android/car/cluster/MainClusterActivity.java
@@ -23,8 +23,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.car.Car;
-import android.car.cluster.CarInstrumentClusterManager;
-import android.car.cluster.ClusterActivityState;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
 import android.car.cluster.sensors.Sensors;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -50,7 +49,6 @@
 import android.widget.Button;
 import android.widget.TextView;
 
-import androidx.car.cluster.navigation.NavigationState;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
@@ -65,7 +63,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.net.URISyntaxException;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -94,7 +91,8 @@
     private static final int MEDIA_FACET_ID = 2;
     private static final int INFO_FACET_ID = 3;
 
-    private static final NavigationState NULL_NAV_STATE = new NavigationState.Builder().build();
+    private static final NavigationStateProto NULL_NAV_STATE =
+            NavigationStateProto.getDefaultInstance();
     private static final int NO_DISPLAY = -1;
 
     private ViewPager mPager;
@@ -315,7 +313,7 @@
     }
 
     @Override
-    public void onNavigationStateChange(NavigationState state) {
+    public void onNavigationStateChange(NavigationStateProto state) {
         Log.d(TAG, "onNavigationStateChange: " + state);
         if (mNavStateController != null) {
             mNavStateController.update(state);
diff --git a/src/android/car/cluster/NavStateController.java b/src/android/car/cluster/NavStateController.java
index 6e37e42..a793032 100644
--- a/src/android/car/cluster/NavStateController.java
+++ b/src/android/car/cluster/NavStateController.java
@@ -16,6 +16,15 @@
 package android.car.cluster;
 
 import android.annotation.Nullable;
+import android.car.cluster.navigation.NavigationState.Destination;
+import android.car.cluster.navigation.NavigationState.Destination.Traffic;
+import android.car.cluster.navigation.NavigationState.Distance;
+import android.car.cluster.navigation.NavigationState.ImageReference;
+import android.car.cluster.navigation.NavigationState.Maneuver;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
+import android.car.cluster.navigation.NavigationState.Road;
+import android.car.cluster.navigation.NavigationState.Step;
+import android.car.cluster.navigation.NavigationState.Timestamp;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -24,17 +33,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import androidx.car.cluster.navigation.Destination;
-import androidx.car.cluster.navigation.Destination.Traffic;
-import androidx.car.cluster.navigation.Distance;
-import androidx.car.cluster.navigation.ImageReference;
-import androidx.car.cluster.navigation.Maneuver;
-import androidx.car.cluster.navigation.NavigationState;
-import androidx.car.cluster.navigation.Segment;
-import androidx.car.cluster.navigation.Step;
-
-import java.time.Duration;
-import java.time.ZonedDateTime;
+import java.time.Instant;
 
 /**
  * View controller for navigation state rendering.
@@ -81,30 +80,36 @@
     /**
      * Updates views to reflect the provided navigation state
      */
-    public void update(@Nullable NavigationState state) {
+    public void update(@Nullable NavigationStateProto state) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Updating nav state: " + state);
         }
-        Step step = state != null && state.getSteps().size() > 0 ? state.getSteps().get(0) : null;
-        Destination destination = state != null && !state.getDestinations().isEmpty()
-                ? state.getDestinations().get(0) : null;
-        ZonedDateTime eta = destination != null ? destination.getEta() : null;
+        Step step = state != null && state.getStepsCount() > 0 ? state.getSteps(0) : null;
+        Destination destination = state != null && state.getDestinationsCount() > 0
+                ? state.getDestinations(0) : null;
         Traffic traffic = destination != null ? destination.getTraffic() : null;
-
-        mEta.setText(eta != null ? formatEta(eta) : null);
+        String eta = destination != null
+                ? destination.getFormattedDurationUntilArrival().isEmpty()
+                    ? formatEta(destination.getEstimatedTimeAtArrival())
+                    : destination.getFormattedDurationUntilArrival()
+                : null;
+        mEta.setText(eta);
         mEta.setTextColor(getTrafficColor(traffic));
         mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
-        setProvidedManeuverIcon(mProvidedManeuver,
-                step != null ? step.getManeuver().getIcon() : null);
+        setProvidedManeuverIcon(mProvidedManeuver, step != null
+                ? step.getManeuver().hasIcon() ? step.getManeuver().getIcon() : null
+                : null);
         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
-        mSegment.setText(state != null ? getSegmentString(state.getCurrentSegment()) : null);
-        mCue.setRichText(step != null ? step.getCue() : null, mImageResolver);
+        mSegment.setText(state != null ? getSegmentString(state.getCurrentRoad()) : null);
+        mCue.setCue(step != null ? step.getCue() : null, mImageResolver);
 
-        if (step != null && step.getLanes().size() > 0) {
-            mProvidedLane.setLanes(step.getLanesImage(), mImageResolver);
-            mProvidedLane.setVisibility(View.VISIBLE);
+        if (step != null && step.getLanesCount() > 0) {
+            if (step.hasLanesImage()) {
+                mProvidedLane.setLanes(step.getLanesImage(), mImageResolver);
+                mProvidedLane.setVisibility(View.VISIBLE);
+            }
 
-            mLane.setLanes(step.getLanes());
+            mLane.setLanes(step.getLanesList());
             mLane.setVisibility(View.VISIBLE);
         } else {
             mLane.setVisibility(View.GONE);
@@ -112,7 +117,6 @@
         }
     }
 
-
     private int getTrafficColor(@Nullable Traffic traffic) {
         if (traffic == Traffic.LOW) {
             return mContext.getColor(R.color.low_traffic);
@@ -125,12 +129,12 @@
         return mContext.getColor(R.color.unknown_traffic);
     }
 
-    private String formatEta(@Nullable ZonedDateTime eta) {
-        ZonedDateTime now = ZonedDateTime.now();
-        Duration duration = Duration.between(now, eta);
-        long seconds = duration.getSeconds();
+    private String formatEta(@Nullable Timestamp eta) {
+        long seconds = eta.getSeconds() - Instant.now().getEpochSecond();
 
-        // TODO: move formatting into common lib somewhere
+        // Round up to the nearest minute
+        seconds = (long) Math.ceil(seconds / 60d) * 60;
+
         long minutes = (seconds / 60) % 60;
         long hours = (seconds / 3600) % 24;
         long days = seconds / (3600 * 24);
@@ -144,7 +148,7 @@
         }
     }
 
-    private String getSegmentString(Segment segment) {
+    private String getSegmentString(Road segment) {
         if (segment != null) {
             return segment.getName();
         }
@@ -291,13 +295,13 @@
     }
 
     private String formatDistance(@Nullable Distance distance) {
-        if (distance == null || distance.getDisplayUnit() == Distance.Unit.UNKNOWN) {
+        if (distance == null || distance.getDisplayUnits() == Distance.Unit.UNKNOWN) {
             return null;
         }
 
         String unit = "";
 
-        switch (distance.getDisplayUnit()) {
+        switch (distance.getDisplayUnits()) {
             case METERS:
                 unit = "m";
                 break;
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index c02a598..631a403 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -13,7 +13,8 @@
     robolectric_android-all-stub \
     Robolectric_all-target \
     mockito-robolectric-prebuilt \
-    truth-prebuilt
+    truth-prebuilt \
+    android.car
 
 LOCAL_INSTRUMENTATION_FOR := DirectRenderingCluster
 
@@ -35,7 +36,8 @@
     robolectric_android-all-stub \
     Robolectric_all-target \
     mockito-robolectric-prebuilt \
-    truth-prebuilt
+    truth-prebuilt \
+    android.car
 
 LOCAL_TEST_PACKAGE := DirectRenderingCluster
 
diff --git a/tests/robotests/src/android/car/cluster/ImageResolverTest.java b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
index 76db419..0c05390 100644
--- a/tests/robotests/src/android/car/cluster/ImageResolverTest.java
+++ b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
@@ -32,71 +32,61 @@
 
     @Before
     public void setup() {
-        mImageResolver = new ImageResolver((uri) -> null);
+        mImageResolver = new ImageResolver((uri, w, h) -> null);
     }
 
     @Test
     public void adjustedSize_widerImageInSquareBox() {
-        assertEquals(new Point(20, 10), mImageResolver.getAdjustedSize(40, 20, 20, 20));
+        assertEquals(new Point(20, 10), mImageResolver.getAdjustedSize(2, 20, 20));
     }
 
     @Test
     public void adjustedSize_tallerImageInSquareBox() {
-        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(20, 40, 20, 20));
+        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(0.5, 20, 20));
     }
 
     @Test
     public void adjustedSize_narrowerImageInSquareBox() {
-        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(5, 10, 20, 20));
+        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(0.5, 20, 20));
     }
 
     @Test
     public void adjustedSize_shorterImageInSquareBox() {
-        assertEquals(new Point(20, 8), mImageResolver.getAdjustedSize(5, 2, 20, 20));
+        assertEquals(new Point(20, 8), mImageResolver.getAdjustedSize(2.5, 20, 20));
     }
 
     @Test
     public void adjustedSize_widerImageInTallRectangle() {
-        assertEquals(new Point(20, 10), mImageResolver.getAdjustedSize(40, 20, 20, 40));
+        assertEquals(new Point(20, 10), mImageResolver.getAdjustedSize(2, 20, 40));
     }
 
     @Test
     public void adjustedSize_tallerImageInTallRectangle() {
-        assertEquals(new Point(20, 40), mImageResolver.getAdjustedSize(5, 10, 20, 40));
+        assertEquals(new Point(20, 40), mImageResolver.getAdjustedSize(0.5, 20, 40));
     }
 
     @Test
     public void adjustedSize_widerImageInWideRectangle() {
-        assertEquals(new Point(40, 20), mImageResolver.getAdjustedSize(10, 5, 40, 20));
+        assertEquals(new Point(40, 20), mImageResolver.getAdjustedSize(2, 40, 20));
     }
 
     @Test
     public void adjustedSize_tallerImageInWideRectangle() {
-        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(5, 10, 40, 20));
-    }
-
-    @Test
-    public void adjustedSize_nullIfNoOriginalWidth() {
-        assertEquals(null, mImageResolver.getAdjustedSize(0, 10, 40, 20));
-    }
-
-    @Test
-    public void adjustedSize_nullIfNoOriginalHeight() {
-        assertEquals(null, mImageResolver.getAdjustedSize(5, 0, 40, 20));
+        assertEquals(new Point(10, 20), mImageResolver.getAdjustedSize(0.5, 40, 20));
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void adjustedSize_exceptionIfRequestedWidthAndHeightNoProvided() {
-        assertEquals(null, mImageResolver.getAdjustedSize(5, 10, 0, 0));
+        assertEquals(null, mImageResolver.getAdjustedSize(0.5, 0, 0));
     }
 
     @Test
     public void adjustedSize_flexibleWidth() {
-        assertEquals(new Point(20, 30), mImageResolver.getAdjustedSize(40, 60, 0, 30));
+        assertEquals(new Point(20, 30), mImageResolver.getAdjustedSize(0.66667, 0, 30));
     }
 
     @Test
     public void adjustedSize_flexibleHeight() {
-        assertEquals(new Point(20, 20), mImageResolver.getAdjustedSize(40, 40, 20, 0));
+        assertEquals(new Point(20, 20), mImageResolver.getAdjustedSize(1, 20, 0));
     }
 }