blob: facab518ef70235d45ce3c7d089e5e750b011167 [file] [log] [blame]
/*
* Copyright (C) 2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.time.Instant;
/**
* View controller for navigation state rendering.
*/
public class NavStateController {
private static final String TAG = "Cluster.NavController";
private Handler mHandler = new Handler();
private LinearLayout mSectionManeuver;
private LinearLayout mSectionNavigation;
private LinearLayout mSectionServiceStatus;
private ImageView mManeuver;
private ImageView mProvidedManeuver;
private LaneView mLane;
private LaneView mProvidedLane;
private TextView mDistance;
private TextView mSegment;
private TextView mEta;
private CueView mCue;
private Context mContext;
private ImageResolver mImageResolver;
/**
* Creates a controller to coordinate updates to the views displaying navigation state
* data.
*
* @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);
mProvidedLane = container.findViewById(R.id.provided_lane);
mDistance = container.findViewById(R.id.distance);
mSegment = container.findViewById(R.id.segment);
mEta = container.findViewById(R.id.eta);
mCue = container.findViewById(R.id.cue);
mContext = container.getContext();
}
public void setImageResolver(@Nullable ImageResolver imageResolver) {
mImageResolver = imageResolver;
}
/**
* Updates views to reflect the provided navigation state
*/
public void update(@Nullable NavigationStateProto state) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Updating nav state: " + state);
}
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
? 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().hasIcon() ? step.getManeuver().getIcon() : null
: null);
mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
mSegment.setText(getSegmentString(state.getCurrentRoad()));
mCue.setCue(step != null ? step.getCue() : null, mImageResolver);
if (step != null && step.getLanesCount() > 0) {
if (step.hasLanesImage()) {
mProvidedLane.setLanes(step.getLanesImage(), mImageResolver);
mProvidedLane.setVisibility(View.VISIBLE);
}
mLane.setLanes(step.getLanesList());
mLane.setVisibility(View.VISIBLE);
} else {
mLane.setVisibility(View.GONE);
mProvidedLane.setVisibility(View.GONE);
}
}
private int getTrafficColor(@Nullable Traffic traffic) {
if (traffic == Traffic.LOW) {
return mContext.getColor(R.color.low_traffic);
} else if (traffic == Traffic.MEDIUM) {
return mContext.getColor(R.color.medium_traffic);
} else if (traffic == Traffic.HIGH) {
return mContext.getColor(R.color.high_traffic);
}
return mContext.getColor(R.color.unknown_traffic);
}
private String formatEta(@Nullable Timestamp eta) {
long seconds = eta.getSeconds() - Instant.now().getEpochSecond();
// 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);
if (days > 0) {
return String.format("%d d %d hr", days, hours);
} else if (hours > 0) {
return String.format("%d hr %d min", hours, minutes);
} else {
return String.format("%d min", minutes);
}
}
private String getSegmentString(Road segment) {
if (segment != null) {
return segment.getName();
}
return null;
}
private void setProvidedManeuverIcon(ImageView view, ImageReference imageReference) {
if (mImageResolver == null || imageReference == null) {
view.setImageBitmap(null);
return;
}
mImageResolver
.getBitmap(imageReference, 0, view.getHeight())
.thenAccept(bitmap -> {
mHandler.post(() -> {
view.setImageBitmap(bitmap);
});
})
.exceptionally(ex -> {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unable to fetch image for maneuver: " + imageReference);
}
return null;
});
}
private Drawable getManeuverIcon(@Nullable Maneuver maneuver) {
if (maneuver == null) {
return null;
}
switch (maneuver.getType()) {
case UNKNOWN:
return null;
case DEPART:
return mContext.getDrawable(R.drawable.direction_depart);
case NAME_CHANGE:
return mContext.getDrawable(R.drawable.direction_new_name_straight);
case KEEP_LEFT:
return mContext.getDrawable(R.drawable.direction_continue_left);
case KEEP_RIGHT:
return mContext.getDrawable(R.drawable.direction_continue_right);
case TURN_SLIGHT_LEFT:
return mContext.getDrawable(R.drawable.direction_turn_slight_left);
case TURN_SLIGHT_RIGHT:
return mContext.getDrawable(R.drawable.direction_turn_slight_right);
case TURN_NORMAL_LEFT:
return mContext.getDrawable(R.drawable.direction_turn_left);
case TURN_NORMAL_RIGHT:
return mContext.getDrawable(R.drawable.direction_turn_right);
case TURN_SHARP_LEFT:
return mContext.getDrawable(R.drawable.direction_turn_sharp_left);
case TURN_SHARP_RIGHT:
return mContext.getDrawable(R.drawable.direction_turn_sharp_right);
case U_TURN_LEFT:
return mContext.getDrawable(R.drawable.direction_uturn_left);
case U_TURN_RIGHT:
return mContext.getDrawable(R.drawable.direction_uturn_right);
case ON_RAMP_SLIGHT_LEFT:
return mContext.getDrawable(R.drawable.direction_on_ramp_slight_left);
case ON_RAMP_SLIGHT_RIGHT:
return mContext.getDrawable(R.drawable.direction_on_ramp_slight_right);
case ON_RAMP_NORMAL_LEFT:
return mContext.getDrawable(R.drawable.direction_on_ramp_left);
case ON_RAMP_NORMAL_RIGHT:
return mContext.getDrawable(R.drawable.direction_on_ramp_right);
case ON_RAMP_SHARP_LEFT:
return mContext.getDrawable(R.drawable.direction_on_ramp_sharp_left);
case ON_RAMP_SHARP_RIGHT:
return mContext.getDrawable(R.drawable.direction_on_ramp_sharp_right);
case ON_RAMP_U_TURN_LEFT:
return mContext.getDrawable(R.drawable.direction_uturn_left);
case ON_RAMP_U_TURN_RIGHT:
return mContext.getDrawable(R.drawable.direction_uturn_right);
case OFF_RAMP_SLIGHT_LEFT:
return mContext.getDrawable(R.drawable.direction_off_ramp_slight_left);
case OFF_RAMP_SLIGHT_RIGHT:
return mContext.getDrawable(R.drawable.direction_off_ramp_slight_right);
case OFF_RAMP_NORMAL_LEFT:
return mContext.getDrawable(R.drawable.direction_off_ramp_left);
case OFF_RAMP_NORMAL_RIGHT:
return mContext.getDrawable(R.drawable.direction_off_ramp_right);
case FORK_LEFT:
return mContext.getDrawable(R.drawable.direction_fork_left);
case FORK_RIGHT:
return mContext.getDrawable(R.drawable.direction_fork_right);
case MERGE_LEFT:
return mContext.getDrawable(R.drawable.direction_merge_left);
case MERGE_RIGHT:
return mContext.getDrawable(R.drawable.direction_merge_right);
case MERGE_SIDE_UNSPECIFIED:
return mContext.getDrawable(R.drawable.direction_merge_unspecified);
case ROUNDABOUT_ENTER:
return mContext.getDrawable(R.drawable.direction_roundabout);
case ROUNDABOUT_EXIT:
return mContext.getDrawable(R.drawable.direction_roundabout);
case ROUNDABOUT_ENTER_AND_EXIT_CW_SHARP_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_sharp_right);
case ROUNDABOUT_ENTER_AND_EXIT_CW_NORMAL_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_right);
case ROUNDABOUT_ENTER_AND_EXIT_CW_SLIGHT_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_slight_right);
case ROUNDABOUT_ENTER_AND_EXIT_CW_STRAIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_straight);
case ROUNDABOUT_ENTER_AND_EXIT_CW_SHARP_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_sharp_left);
case ROUNDABOUT_ENTER_AND_EXIT_CW_NORMAL_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_left);
case ROUNDABOUT_ENTER_AND_EXIT_CW_SLIGHT_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_cw_slight_left);
case ROUNDABOUT_ENTER_AND_EXIT_CW_U_TURN:
return mContext.getDrawable(R.drawable.direction_uturn_right);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_SHARP_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_sharp_right);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_NORMAL_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_right);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_SLIGHT_RIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_slight_right);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_STRAIGHT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_straight);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_SHARP_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_sharp_left);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_NORMAL_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_left);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_SLIGHT_LEFT:
return mContext.getDrawable(R.drawable.direction_roundabout_ccw_slight_left);
case ROUNDABOUT_ENTER_AND_EXIT_CCW_U_TURN:
return mContext.getDrawable(R.drawable.direction_uturn_left);
case STRAIGHT:
return mContext.getDrawable(R.drawable.direction_continue);
case FERRY_BOAT:
return mContext.getDrawable(R.drawable.direction_close);
case FERRY_TRAIN:
return mContext.getDrawable(R.drawable.direction_close);
case DESTINATION:
return mContext.getDrawable(R.drawable.direction_arrive);
case DESTINATION_STRAIGHT:
return mContext.getDrawable(R.drawable.direction_arrive_straight);
case DESTINATION_LEFT:
return mContext.getDrawable(R.drawable.direction_arrive_left);
case DESTINATION_RIGHT:
return mContext.getDrawable(R.drawable.direction_arrive_right);
}
return null;
}
private String formatDistance(@Nullable Distance distance) {
if (distance == null || distance.getDisplayUnits() == Distance.Unit.UNKNOWN) {
return null;
}
String unit = "";
switch (distance.getDisplayUnits()) {
case METERS:
unit = "m";
break;
case KILOMETERS:
unit = "km";
break;
case MILES:
unit = "mi";
break;
case YARDS:
unit = "yd";
break;
case FEET:
unit = "ft";
break;
}
return String.format("In %s %s", distance.getDisplayValue(), unit);
}
}