Merge stage-aosp-master to aosp-master - DO NOT MERGE

Change-Id: I34d08cf1d252b79ed2e0d6e2435e95fe04e71761
diff --git a/res/layout/include_navigation_state.xml b/res/layout/include_navigation_state.xml
index af04cf1..5cd6ff5 100644
--- a/res/layout/include_navigation_state.xml
+++ b/res/layout/include_navigation_state.xml
@@ -5,6 +5,7 @@
               android:orientation="horizontal">
 
     <LinearLayout
+        android:id="@+id/section_maneuver"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical">
@@ -24,6 +25,21 @@
     </LinearLayout>
 
     <LinearLayout
+        android:id="@+id/section_service_status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/service_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Rerouting..."
+            android:textSize="@dimen/distance_text_size"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/section_navigation"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical">
diff --git a/res/values/config.xml b/res/values/config.xml
index 89998f3..d06dff2 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,4 +19,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!--  Activity to present for free navigation -->
     <string name="freeNavigationIntent" translatable="false">intent:#Intent;component=android.car.cluster/.FakeFreeNavigationActivity;launchFlags=0x24000000;end</string>
+    <bool name="navigationOnly">false</bool>
 </resources>
diff --git a/src/android/car/cluster/ActivityMonitor.java b/src/android/car/cluster/ActivityMonitor.java
index e7fb89c..28c8147 100644
--- a/src/android/car/cluster/ActivityMonitor.java
+++ b/src/android/car/cluster/ActivityMonitor.java
@@ -16,6 +16,7 @@
 package android.car.cluster;
 
 import android.annotation.Nullable;
+import android.annotation.UiThread;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
@@ -23,6 +24,7 @@
 import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -35,6 +37,12 @@
 /**
  * Top activity monitor, allows listeners to be notified when a new activity comes to the foreground
  * on a particular device.
+ *
+ * As a sanity check {@link #notifyTopActivities} is handed to the UI thread because it is triggered
+ * by {@link #mProcessObserver} and {@link #mTaskStackListener}, which may be called by background
+ * threads.
+ *
+ * {@link #start} and {@link #stop} should be called only by the UI thread to prevent possible NPEs.
  */
 public class ActivityMonitor {
     private static final String TAG = "Cluster.ActivityMonitor";
@@ -54,11 +62,17 @@
     private final Map<Integer, Set<ActivityListener>> mListeners = new HashMap<>();
     private final Handler mHandler = new Handler();
     private final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
+        /**
+         * Note: This function may sometimes be called from a background thread
+         */
         @Override
         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
             notifyTopActivities();
         }
 
+        /**
+         * Note: This function may sometimes be called from a background thread
+         */
         @Override
         public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { }
 
@@ -68,6 +82,9 @@
         }
     };
     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+        /**
+         * Note: This function may sometimes be called from a background thread
+         */
         @Override
         public void onTaskStackChanged() {
             Log.i(TAG, "onTaskStackChanged");
@@ -79,7 +96,7 @@
      * Registers a new listener to receive activity updates on a particular display
      *
      * @param displayId identifier of the display to monitor
-     * @param listener listener to be notified
+     * @param listener  listener to be notified
      */
     public void addListener(int displayId, ActivityListener listener) {
         mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).add(listener);
@@ -94,7 +111,10 @@
 
     /**
      * Starts monitoring activity changes. {@link #stop()} should be invoked to release resources.
+     *
+     * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
      */
+    @UiThread
     public void start() {
         mActivityManager = ActivityManager.getService();
         // Monitoring both listeners are necessary as there are cases where one listener cannot
@@ -111,11 +131,17 @@
 
     /**
      * Stops monitoring activity changes. Should be invoked when this monitor is not longer used.
+     *
+     * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
      */
+    @UiThread
     public void stop() {
         if (mActivityManager == null) {
             return;
         }
+        if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
+            Log.w(TAG, "stop() is called on non-UI thread. May cause NPE");
+        }
         try {
             mActivityManager.unregisterProcessObserver(mProcessObserver);
             mActivityManager.unregisterTaskStackListener(mTaskStackListener);
@@ -126,12 +152,18 @@
     }
 
     /**
-     * Notifies listeners on changes of top activities. {@link ActivityManager} might trigger
-     * updates on threads different than UI.
+     * Notifies listeners on changes of top activities.
+     *
+     * Note: This method may sometimes be called by background threads, so it is synchronized on
+     * the UI thread with mHandler.post()
      */
     private void notifyTopActivities() {
         mHandler.post(() -> {
             try {
+                // return if the activity monitor is no longer used
+                if (mActivityManager == null) {
+                    return;
+                }
                 List<StackInfo> infos = mActivityManager.getAllStackInfos();
                 for (StackInfo info : infos) {
                     Set<ActivityListener> listeners = mListeners.get(info.displayId);
diff --git a/src/android/car/cluster/ClusterRenderingService.java b/src/android/car/cluster/ClusterRenderingService.java
index f27b036..a030d4a 100644
--- a/src/android/car/cluster/ClusterRenderingService.java
+++ b/src/android/car/cluster/ClusterRenderingService.java
@@ -15,21 +15,31 @@
  */
 package android.car.cluster;
 
+import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static java.lang.Integer.parseInt;
 
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.car.CarNotConnectedException;
 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
 import android.car.cluster.renderer.InstrumentClusterRenderingService;
 import android.car.cluster.renderer.NavigationRenderer;
 import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -56,8 +66,7 @@
 public class ClusterRenderingService extends InstrumentClusterRenderingService implements
         ImageResolver.BitmapFetcher {
     private static final String TAG = "Cluster.Service";
-
-    private static final int NO_DISPLAY = -1;
+    private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
 
     static final int NAV_STATE_EVENT_ID = 1;
     static final String LOCAL_BINDING_ACTION = "local";
@@ -65,9 +74,17 @@
 
     private List<ServiceClient> mClients = new ArrayList<>();
     private ClusterDisplayProvider mDisplayProvider;
-    private int mDisplayId = NO_DISPLAY;
+
+    private int mClusterDisplayId = INVALID_DISPLAY;
+
+    private boolean mInstrumentClusterHelperReady;
+
     private final IBinder mLocalBinder = new LocalBinder();
     private final ImageResolver mImageResolver = new ImageResolver(this);
+    private final Handler mHandler = new Handler();
+    private final Runnable mLaunchMainActivity = this::launchMainActivity;
+
+    private final UserReceiver mUserReceiver = new UserReceiver();
 
     public interface ServiceClient {
         void onKeyEvent(KeyEvent keyEvent);
@@ -82,11 +99,15 @@
     }
 
     private final DisplayListener mDisplayListener = new DisplayListener() {
+        // Called in the main thread, since ClusterDisplayProvider.DisplayListener was registered
+        // with null handler.
         @Override
         public void onDisplayAdded(int displayId) {
             Log.i(TAG, "Cluster display found, displayId: " + displayId);
-            mDisplayId = displayId;
-            launchMainActivity();
+            mClusterDisplayId = displayId;
+            if (mInstrumentClusterHelperReady) {
+                mHandler.post(mLaunchMainActivity);
+            }
         }
 
         @Override
@@ -102,7 +123,7 @@
 
     public void setActivityLaunchOptions(int displayId, ClusterActivityState state) {
         try {
-            ActivityOptions options = displayId != Display.INVALID_DISPLAY
+            ActivityOptions options = displayId != INVALID_DISPLAY
                     ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
                     : null;
             setClusterActivityLaunchOptions(CarInstrumentClusterManager.CATEGORY_NAVIGATION,
@@ -136,9 +157,15 @@
     @Override
     public IBinder onBind(Intent intent) {
         Log.d(TAG, "onBind, intent: " + intent);
-        return LOCAL_BINDING_ACTION.equals(intent.getAction())
-                ? mLocalBinder
-                : super.onBind(intent);
+        if (LOCAL_BINDING_ACTION.equals(intent.getAction())) {
+            return mLocalBinder;
+        }
+        IBinder binder = super.onBind(intent);
+        mInstrumentClusterHelperReady = true;
+        if (mClusterDisplayId != INVALID_DISPLAY) {
+            mHandler.post(mLaunchMainActivity);
+        }
+        return binder;
     }
 
     @Override
@@ -146,15 +173,60 @@
         super.onCreate();
         Log.d(TAG, "onCreate");
         mDisplayProvider = new ClusterDisplayProvider(this, mDisplayListener);
+
+        mUserReceiver.register(this);
+    }
+
+    public void onDestroy() {
+        super.onDestroy();
+        mUserReceiver.unregister(this);
     }
 
     private void launchMainActivity() {
+        mHandler.removeCallbacks(mLaunchMainActivity);
         ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchDisplayId(mDisplayId);
-        Intent intent = new Intent(this, MainClusterActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        startActivityAsUser(intent, options.toBundle(), UserHandle.SYSTEM);
-        Log.i(TAG, String.format("launching main activity: %s (display: %d)", intent, mDisplayId));
+        options.setLaunchDisplayId(mClusterDisplayId);
+        boolean useNavigationOnly = getResources().getBoolean(R.bool.navigationOnly);
+        Intent intent;
+        int userId = UserHandle.USER_SYSTEM;
+        if (useNavigationOnly) {
+            intent = getNavigationActivityIntent(mClusterDisplayId);
+            if (intent == null) {
+                mHandler.postDelayed(mLaunchMainActivity, NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS);
+                return;
+            }
+            userId = ActivityManager.getCurrentUser();
+            startFixedActivityModeForDisplayAndUser(intent, options, userId);
+        } else {
+            intent = getMainClusterActivityIntent();
+            startActivityAsUser(intent, options.toBundle(), UserHandle.SYSTEM);
+        }
+        Log.i(TAG, "launching main activity=" + intent + ", display=" + mClusterDisplayId
+                + ", userId=" + userId);
+    }
+
+    private Intent getMainClusterActivityIntent() {
+        return new Intent(this, MainClusterActivity.class).setFlags(FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    private Intent getNavigationActivityIntent(int displayId) {
+        ComponentName component = MainClusterActivity.getNavigationActivity(this);
+        if (component == null) {
+            Log.e(TAG, "Failed to resolve the navigation activity");
+            return null;
+        }
+        Rect displaySize = new Rect(0, 0, 320, 240);  // Arbitrary size, better than nothing.
+        DisplayManager dm = (DisplayManager) getSystemService(DisplayManager.class);
+        Display display = dm.getDisplay(displayId);
+        if (display != null) {
+            display.getRectSize(displaySize);
+        }
+        return new Intent(Intent.ACTION_MAIN)
+            .setComponent(component)
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            .putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE,
+                ClusterActivityState.create(/* visible= */ true,
+                    /* unobscuredBounds= */ displaySize).toBundle());
     }
 
     @Override
@@ -305,4 +377,27 @@
             }
         }
     }
+
+    private class UserReceiver extends BroadcastReceiver {
+        void register(Context context) {
+            IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
+            context.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null, null);
+        }
+
+        void unregister(Context context) {
+            context.unregisterReceiver(this);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Broadcast received: " + intent);
+            }
+            int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId == ActivityManager.getCurrentUser() &&
+                mInstrumentClusterHelperReady && mClusterDisplayId != INVALID_DISPLAY) {
+                mHandler.post(mLaunchMainActivity);
+            }
+        }
+    }
 }
diff --git a/src/android/car/cluster/ImageResolver.java b/src/android/car/cluster/ImageResolver.java
index 5053d67..445b7e6 100644
--- a/src/android/car/cluster/ImageResolver.java
+++ b/src/android/car/cluster/ImageResolver.java
@@ -44,7 +44,12 @@
         /**
          * Returns a {@link Bitmap} given a request Uri and dimensions
          */
-        Bitmap getBitmap(Uri uri, int width, int height) throws IllegalArgumentException;
+        Bitmap getBitmap(Uri uri, int width, int height);
+
+        /**
+         * Returns a {@link Bitmap} given a request Uri, dimensions, and offLanesAlpha value
+         */
+        Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha) throws IllegalArgumentException;
     }
 
     /**
@@ -64,6 +69,22 @@
      */
     @NonNull
     public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
+        return getBitmap(img, width, height, 1f);
+    }
+
+    /**
+     * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+     * 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 height        required height, or 0 if height is flexible based on width.
+     * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+     *                      guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+     */
+    @NonNull
+    public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height,
+            float offLanesAlpha) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
                     img.getContentUri(), width, height));
@@ -80,9 +101,9 @@
             Uri uri = Uri.parse(img.getContentUri());
             Bitmap bitmap = null;
             try {
-                bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y);
+                bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y, offLanesAlpha);
             } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Bitmap must have positive width and height");
+                Log.e(TAG, e.getMessage());
             }
             if (bitmap == null) {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -110,12 +131,28 @@
     @NonNull
     public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
             @NonNull List<ImageReference> imgs, int width, int height) {
+        return getBitmaps(imgs, width, height, 1f);
+    }
+
+    /**
+     * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+     * 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 height        required height, or 0 if height is flexible based on width.
+     * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+     *                      guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+     */
+    @NonNull
+    public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+            @NonNull List<ImageReference> imgs, int width, int height, float offLanesAlpha) {
         CompletableFuture<Map<ImageReference, Bitmap>> future = new CompletableFuture<>();
 
         Map<ImageReference, CompletableFuture<Bitmap>> bitmapFutures = imgs.stream().collect(
                 Collectors.toMap(
                         img -> img,
-                        img -> getBitmap(img, width, height)));
+                        img -> getBitmap(img, width, height, offLanesAlpha)));
 
         CompletableFuture.allOf(bitmapFutures.values().toArray(new CompletableFuture[0]))
                 .thenAccept(v -> {
diff --git a/src/android/car/cluster/MainClusterActivity.java b/src/android/car/cluster/MainClusterActivity.java
index e5f1311..5e19a42 100644
--- a/src/android/car/cluster/MainClusterActivity.java
+++ b/src/android/car/cluster/MainClusterActivity.java
@@ -110,7 +110,6 @@
     private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
     private static final int NAVIGATION_ACTIVITY_RELAUNCH_DELAY_MS = 5000;
 
-    private UserReceiver mUserReceiver;
     private ActivityMonitor mActivityMonitor = new ActivityMonitor();
     private final Handler mHandler = new Handler();
     private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
@@ -173,33 +172,6 @@
         mClusterViewModel.setCurrentNavigationActivity(activity);
     };
 
-    private static class UserReceiver extends BroadcastReceiver {
-        private WeakReference<MainClusterActivity> mActivity;
-
-        UserReceiver(MainClusterActivity activity) {
-            mActivity = new WeakReference<>(activity);
-        }
-
-        public void register(Context context) {
-            IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
-            intentFilter.addAction(ACTION_USER_SWITCHED);
-            context.registerReceiver(this, intentFilter);
-        }
-
-        public void unregister(Context context) {
-            context.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            MainClusterActivity activity = mActivity.get();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Broadcast received: " + intent);
-            }
-            activity.tryLaunchNavigationActivity();
-        }
-    }
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -258,9 +230,6 @@
 
         mActivityMonitor.start();
 
-        mUserReceiver = new UserReceiver(this);
-        mUserReceiver.register(this);
-
         InMemoryPhoneBook.init(this);
 
         PhoneFragmentViewModel phoneViewModel = ViewModelProviders.of(this).get(
@@ -294,7 +263,6 @@
     protected void onDestroy() {
         super.onDestroy();
         Log.d(TAG, "onDestroy");
-        mUserReceiver.unregister(this);
         mActivityMonitor.stop();
         if (mService != null) {
             mService.unregisterClient(this);
@@ -414,7 +382,7 @@
         }
         mHandler.removeCallbacks(mRetryLaunchNavigationActivity);
 
-        ComponentName navigationActivity = getNavigationActivity();
+        ComponentName navigationActivity = getNavigationActivity(this);
         mClusterViewModel.setFreeNavigationActivity(navigationActivity);
 
         try {
@@ -456,10 +424,10 @@
      * <li>Let the user select one from settings.
      * </ul>
      */
-    private ComponentName getNavigationActivity() {
-        PackageManager pm = getPackageManager();
+    static ComponentName getNavigationActivity(Context context) {
+        PackageManager pm = context.getPackageManager();
         int userId = ActivityManager.getCurrentUser();
-        String intentString = getString(R.string.freeNavigationIntent);
+        String intentString = context.getString(R.string.freeNavigationIntent);
 
         if (intentString == null) {
             Log.w(TAG, "No free navigation activity defined");
diff --git a/src/android/car/cluster/NavStateController.java b/src/android/car/cluster/NavStateController.java
index 7ac32ac..facab51 100644
--- a/src/android/car/cluster/NavStateController.java
+++ b/src/android/car/cluster/NavStateController.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import java.time.Instant;
@@ -43,6 +44,10 @@
 
     private Handler mHandler = new Handler();
 
+    private LinearLayout mSectionManeuver;
+    private LinearLayout mSectionNavigation;
+    private LinearLayout mSectionServiceStatus;
+
     private ImageView mManeuver;
     private ImageView mProvidedManeuver;
     private LaneView mLane;
@@ -61,6 +66,10 @@
      * @param container {@link View} containing the navigation state views
      */
     public NavStateController(View container) {
+        mSectionManeuver = container.findViewById(R.id.section_maneuver);
+        mSectionNavigation = container.findViewById(R.id.section_navigation);
+        mSectionServiceStatus = container.findViewById(R.id.section_service_status);
+
         mManeuver = container.findViewById(R.id.maneuver);
         mProvidedManeuver = container.findViewById(R.id.provided_maneuver);
         mLane = container.findViewById(R.id.lane);
@@ -84,8 +93,24 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "Updating nav state: " + state);
         }
-        Step step = state != null && state.getStepsCount() > 0 ? state.getSteps(0) : null;
-        Destination destination = state != null && state.getDestinationsCount() > 0
+
+        if (state == null) {
+            return;
+        }
+
+        if (state.getServiceStatus() == NavigationStateProto.ServiceStatus.REROUTING) {
+            mSectionManeuver.setVisibility(View.INVISIBLE);
+            mSectionNavigation.setVisibility(View.INVISIBLE);
+            mSectionServiceStatus.setVisibility(View.VISIBLE);
+            return;
+        } else {
+            mSectionManeuver.setVisibility(View.VISIBLE);
+            mSectionNavigation.setVisibility(View.VISIBLE);
+            mSectionServiceStatus.setVisibility(View.GONE);
+        }
+
+        Step step = state.getStepsCount() > 0 ? state.getSteps(0) : null;
+        Destination destination = state.getDestinationsCount() > 0
                 ? state.getDestinations(0) : null;
         Traffic traffic = destination != null ? destination.getTraffic() : null;
         String eta = destination != null
@@ -100,7 +125,7 @@
                 ? step.getManeuver().hasIcon() ? step.getManeuver().getIcon() : null
                 : null);
         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
-        mSegment.setText(state != null ? getSegmentString(state.getCurrentRoad()) : null);
+        mSegment.setText(getSegmentString(state.getCurrentRoad()));
         mCue.setCue(step != null ? step.getCue() : null, mImageResolver);
 
         if (step != null && step.getLanesCount() > 0) {
diff --git a/tests/robotests/src/android/car/cluster/ImageResolverTest.java b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
index 0c05390..9b39659 100644
--- a/tests/robotests/src/android/car/cluster/ImageResolverTest.java
+++ b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
@@ -17,7 +17,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.net.Uri;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -32,7 +34,17 @@
 
     @Before
     public void setup() {
-        mImageResolver = new ImageResolver((uri, w, h) -> null);
+        mImageResolver = new ImageResolver(new ImageResolver.BitmapFetcher() {
+            @Override
+            public Bitmap getBitmap(Uri uri, int width, int height) {
+                return null;
+            }
+
+            @Override
+            public Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha) {
+                return null;
+            }
+        });
     }
 
     @Test