• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.car.cluster;
17 
18 import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.NORMAL;
19 import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.REROUTING;
20 import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.SERVICE_STATUS_UNSPECIFIED;
21 
22 import android.annotation.Nullable;
23 import android.car.cluster.navigation.NavigationState.Destination;
24 import android.car.cluster.navigation.NavigationState.Destination.Traffic;
25 import android.car.cluster.navigation.NavigationState.Distance;
26 import android.car.cluster.navigation.NavigationState.ImageReference;
27 import android.car.cluster.navigation.NavigationState.Maneuver;
28 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
29 import android.car.cluster.navigation.NavigationState.Road;
30 import android.car.cluster.navigation.NavigationState.Step;
31 import android.car.cluster.navigation.NavigationState.Timestamp;
32 import android.content.Context;
33 import android.graphics.drawable.Drawable;
34 import android.os.Handler;
35 import android.util.Log;
36 import android.util.TypedValue;
37 import android.view.View;
38 import android.widget.ImageView;
39 import android.widget.LinearLayout;
40 import android.widget.TextView;
41 
42 import java.time.Instant;
43 
44 /**
45  * View controller for navigation state rendering.
46  */
47 public class NavStateController {
48     private static final String TAG = "Cluster.NavController";
49 
50     private Handler mHandler = new Handler();
51 
52     private View mNavigationState;
53     private LinearLayout mSectionManeuver;
54     private LinearLayout mSectionNavigation;
55     private LinearLayout mSectionServiceStatus;
56 
57     private ImageView mManeuver;
58     private ImageView mProvidedManeuver;
59     private LaneView mLane;
60     private LaneView mProvidedLane;
61     private TextView mDistance;
62     private TextView mSegment;
63     private TextView mEta;
64     private CueView mCue;
65     private Context mContext;
66     private ImageResolver mImageResolver;
67 
68     /**
69      * Creates a controller to coordinate updates to the views displaying navigation state
70      * data.
71      *
72      * @param container {@link View} containing the navigation state views
73      */
NavStateController(View container)74     public NavStateController(View container) {
75         mNavigationState = container;
76         mSectionManeuver = container.findViewById(R.id.section_maneuver);
77         mSectionNavigation = container.findViewById(R.id.section_navigation);
78         mSectionServiceStatus = container.findViewById(R.id.section_service_status);
79 
80         mManeuver = container.findViewById(R.id.maneuver);
81         mProvidedManeuver = container.findViewById(R.id.provided_maneuver);
82         mLane = container.findViewById(R.id.lane);
83         mProvidedLane = container.findViewById(R.id.provided_lane);
84         mDistance = container.findViewById(R.id.distance);
85         mSegment = container.findViewById(R.id.segment);
86         mEta = container.findViewById(R.id.eta);
87         mCue = container.findViewById(R.id.cue);
88 
89         mContext = container.getContext();
90     }
91 
hideNavigationStateInfo()92     public void hideNavigationStateInfo() {
93         mNavigationState.setVisibility(View.INVISIBLE);
94     }
95 
showNavigationStateInfo()96     public void showNavigationStateInfo() {
97         mNavigationState.setVisibility(View.VISIBLE);
98     }
99 
setImageResolver(@ullable ImageResolver imageResolver)100     public void setImageResolver(@Nullable ImageResolver imageResolver) {
101         mImageResolver = imageResolver;
102     }
103 
104     /**
105      * Updates views to reflect the provided navigation state
106      */
update(@ullable NavigationStateProto state)107     public void update(@Nullable NavigationStateProto state) {
108         if (Log.isLoggable(TAG, Log.DEBUG)) {
109             Log.d(TAG, "Updating nav state: " + state);
110         }
111 
112         if (state == null) {
113             return;
114         }
115 
116         NavigationStateProto.ServiceStatus serviceStatus = state.getServiceStatus();
117         if (serviceStatus == SERVICE_STATUS_UNSPECIFIED) {
118             mSectionManeuver.setVisibility(View.INVISIBLE);
119             mSectionNavigation.setVisibility(View.INVISIBLE);
120             mSectionServiceStatus.setVisibility(View.INVISIBLE);
121             return;
122         } else if (serviceStatus == REROUTING) {
123             mSectionManeuver.setVisibility(View.INVISIBLE);
124             mSectionNavigation.setVisibility(View.INVISIBLE);
125             mSectionServiceStatus.setVisibility(View.VISIBLE);
126             return;
127         } else {
128             mSectionManeuver.setVisibility(View.VISIBLE);
129             mSectionNavigation.setVisibility(View.VISIBLE);
130             mSectionServiceStatus.setVisibility(View.GONE);
131         }
132 
133         Step step = state.getStepsCount() > 0 ? state.getSteps(0) : null;
134 
135         // Get alpha based on is_imminent
136         float alpha = (step != null && !step.getIsImminent())
137                 ? getAlphaFromResource(R.dimen.non_imminent_alpha)
138                 : 1f;
139 
140         Destination destination = state.getDestinationsCount() > 0
141                 ? state.getDestinations(0) : null;
142         Traffic traffic = destination != null ? destination.getTraffic() : null;
143         String eta = destination != null
144                 ? destination.getFormattedDurationUntilArrival().isEmpty()
145                 ? formatEta(destination.getEstimatedTimeAtArrival())
146                 : destination.getFormattedDurationUntilArrival()
147                 : null;
148         mEta.setText(eta);
149         mEta.setTextColor(getTrafficColor(traffic));
150         mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
151         setProvidedManeuverIcon(mProvidedManeuver, step != null
152                 ? step.getManeuver().hasIcon() ? step.getManeuver().getIcon() : null
153                 : null);
154         mManeuver.setImageAlpha((int) (alpha * 255));
155         mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
156         mDistance.setAlpha(alpha);
157         mSegment.setText(getSegmentString(state.getCurrentRoad()));
158         mCue.setCue(step != null ? step.getCue() : null, mImageResolver, alpha);
159 
160         if (step != null && step.getLanesCount() > 0) {
161             if (step.hasLanesImage()) {
162                 mProvidedLane.setLanes(step.getLanesImage(), mImageResolver);
163                 mProvidedLane.setVisibility(View.VISIBLE);
164             }
165 
166             mLane.setLanes(step.getLanesList(), alpha);
167             mLane.setVisibility(View.VISIBLE);
168         } else {
169             mLane.setVisibility(View.GONE);
170             mProvidedLane.setVisibility(View.GONE);
171         }
172 
173     }
174 
175     /**
176      * Get float value from dimens.xml, it only works for float format
177      */
getAlphaFromResource(int alphaId)178     private float getAlphaFromResource(int alphaId) {
179         TypedValue typedValue = new TypedValue();
180         mContext.getResources().getValue(alphaId, typedValue, true);
181         return typedValue.getFloat();
182     }
183 
getTrafficColor(@ullable Traffic traffic)184     private int getTrafficColor(@Nullable Traffic traffic) {
185         if (traffic == Traffic.LOW) {
186             return mContext.getColor(R.color.low_traffic);
187         } else if (traffic == Traffic.MEDIUM) {
188             return mContext.getColor(R.color.medium_traffic);
189         } else if (traffic == Traffic.HIGH) {
190             return mContext.getColor(R.color.high_traffic);
191         }
192 
193         return mContext.getColor(R.color.unknown_traffic);
194     }
195 
formatEta(@ullable Timestamp eta)196     private String formatEta(@Nullable Timestamp eta) {
197         long seconds = eta.getSeconds() - Instant.now().getEpochSecond();
198 
199         // Round up to the nearest minute
200         seconds = (long) Math.ceil(seconds / 60d) * 60;
201 
202         long minutes = (seconds / 60) % 60;
203         long hours = (seconds / 3600) % 24;
204         long days = seconds / (3600 * 24);
205 
206         if (days > 0) {
207             return String.format("%d d %d hr", days, hours);
208         } else if (hours > 0) {
209             return String.format("%d hr %d min", hours, minutes);
210         } else {
211             return String.format("%d min", minutes);
212         }
213     }
214 
getSegmentString(Road segment)215     private String getSegmentString(Road segment) {
216         if (segment != null) {
217             return segment.getName();
218         }
219 
220         return null;
221     }
222 
setProvidedManeuverIcon(ImageView view, ImageReference imageReference)223     private void setProvidedManeuverIcon(ImageView view, ImageReference imageReference) {
224         if (mImageResolver == null || imageReference == null) {
225             view.setImageBitmap(null);
226             return;
227         }
228 
229         mImageResolver
230                 .getBitmap(imageReference, 0, view.getHeight())
231                 .thenAccept(bitmap -> {
232                     mHandler.post(() -> {
233                         view.setImageBitmap(bitmap);
234                     });
235                 })
236                 .exceptionally(ex -> {
237                     if (Log.isLoggable(TAG, Log.DEBUG)) {
238                         Log.d(TAG, "Unable to fetch image for maneuver: " + imageReference);
239                     }
240                     return null;
241                 });
242     }
243 
getManeuverIcon(@ullable Maneuver maneuver)244     private Drawable getManeuverIcon(@Nullable Maneuver maneuver) {
245         if (maneuver == null) {
246             return null;
247         }
248         switch (maneuver.getType()) {
249             case UNKNOWN:
250                 return null;
251             case DEPART:
252                 return mContext.getDrawable(R.drawable.direction_depart);
253             case NAME_CHANGE:
254                 return mContext.getDrawable(R.drawable.direction_new_name_straight);
255             case KEEP_LEFT:
256                 return mContext.getDrawable(R.drawable.direction_continue_left);
257             case KEEP_RIGHT:
258                 return mContext.getDrawable(R.drawable.direction_continue_right);
259             case TURN_SLIGHT_LEFT:
260                 return mContext.getDrawable(R.drawable.direction_turn_slight_left);
261             case TURN_SLIGHT_RIGHT:
262                 return mContext.getDrawable(R.drawable.direction_turn_slight_right);
263             case TURN_NORMAL_LEFT:
264                 return mContext.getDrawable(R.drawable.direction_turn_left);
265             case TURN_NORMAL_RIGHT:
266                 return mContext.getDrawable(R.drawable.direction_turn_right);
267             case TURN_SHARP_LEFT:
268                 return mContext.getDrawable(R.drawable.direction_turn_sharp_left);
269             case TURN_SHARP_RIGHT:
270                 return mContext.getDrawable(R.drawable.direction_turn_sharp_right);
271             case U_TURN_LEFT:
272                 return mContext.getDrawable(R.drawable.direction_uturn_left);
273             case U_TURN_RIGHT:
274                 return mContext.getDrawable(R.drawable.direction_uturn_right);
275             case ON_RAMP_SLIGHT_LEFT:
276                 return mContext.getDrawable(R.drawable.direction_on_ramp_slight_left);
277             case ON_RAMP_SLIGHT_RIGHT:
278                 return mContext.getDrawable(R.drawable.direction_on_ramp_slight_right);
279             case ON_RAMP_NORMAL_LEFT:
280                 return mContext.getDrawable(R.drawable.direction_on_ramp_left);
281             case ON_RAMP_NORMAL_RIGHT:
282                 return mContext.getDrawable(R.drawable.direction_on_ramp_right);
283             case ON_RAMP_SHARP_LEFT:
284                 return mContext.getDrawable(R.drawable.direction_on_ramp_sharp_left);
285             case ON_RAMP_SHARP_RIGHT:
286                 return mContext.getDrawable(R.drawable.direction_on_ramp_sharp_right);
287             case ON_RAMP_U_TURN_LEFT:
288                 return mContext.getDrawable(R.drawable.direction_uturn_left);
289             case ON_RAMP_U_TURN_RIGHT:
290                 return mContext.getDrawable(R.drawable.direction_uturn_right);
291             case OFF_RAMP_SLIGHT_LEFT:
292                 return mContext.getDrawable(R.drawable.direction_off_ramp_slight_left);
293             case OFF_RAMP_SLIGHT_RIGHT:
294                 return mContext.getDrawable(R.drawable.direction_off_ramp_slight_right);
295             case OFF_RAMP_NORMAL_LEFT:
296                 return mContext.getDrawable(R.drawable.direction_off_ramp_left);
297             case OFF_RAMP_NORMAL_RIGHT:
298                 return mContext.getDrawable(R.drawable.direction_off_ramp_right);
299             case FORK_LEFT:
300                 return mContext.getDrawable(R.drawable.direction_fork_left);
301             case FORK_RIGHT:
302                 return mContext.getDrawable(R.drawable.direction_fork_right);
303             case MERGE_LEFT:
304                 return mContext.getDrawable(R.drawable.direction_merge_left);
305             case MERGE_RIGHT:
306                 return mContext.getDrawable(R.drawable.direction_merge_right);
307             case MERGE_SIDE_UNSPECIFIED:
308                 return mContext.getDrawable(R.drawable.direction_merge_unspecified);
309             case ROUNDABOUT_ENTER:
310                 return mContext.getDrawable(R.drawable.direction_roundabout);
311             case ROUNDABOUT_EXIT:
312                 return mContext.getDrawable(R.drawable.direction_roundabout);
313             case ROUNDABOUT_ENTER_AND_EXIT_CW_SHARP_RIGHT:
314                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_sharp_right);
315             case ROUNDABOUT_ENTER_AND_EXIT_CW_NORMAL_RIGHT:
316                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_right);
317             case ROUNDABOUT_ENTER_AND_EXIT_CW_SLIGHT_RIGHT:
318                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_slight_right);
319             case ROUNDABOUT_ENTER_AND_EXIT_CW_STRAIGHT:
320                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_straight);
321             case ROUNDABOUT_ENTER_AND_EXIT_CW_SHARP_LEFT:
322                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_sharp_left);
323             case ROUNDABOUT_ENTER_AND_EXIT_CW_NORMAL_LEFT:
324                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_left);
325             case ROUNDABOUT_ENTER_AND_EXIT_CW_SLIGHT_LEFT:
326                 return mContext.getDrawable(R.drawable.direction_roundabout_cw_slight_left);
327             case ROUNDABOUT_ENTER_AND_EXIT_CW_U_TURN:
328                 return mContext.getDrawable(R.drawable.direction_uturn_right);
329             case ROUNDABOUT_ENTER_AND_EXIT_CCW_SHARP_RIGHT:
330                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_sharp_right);
331             case ROUNDABOUT_ENTER_AND_EXIT_CCW_NORMAL_RIGHT:
332                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_right);
333             case ROUNDABOUT_ENTER_AND_EXIT_CCW_SLIGHT_RIGHT:
334                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_slight_right);
335             case ROUNDABOUT_ENTER_AND_EXIT_CCW_STRAIGHT:
336                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_straight);
337             case ROUNDABOUT_ENTER_AND_EXIT_CCW_SHARP_LEFT:
338                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_sharp_left);
339             case ROUNDABOUT_ENTER_AND_EXIT_CCW_NORMAL_LEFT:
340                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_left);
341             case ROUNDABOUT_ENTER_AND_EXIT_CCW_SLIGHT_LEFT:
342                 return mContext.getDrawable(R.drawable.direction_roundabout_ccw_slight_left);
343             case ROUNDABOUT_ENTER_AND_EXIT_CCW_U_TURN:
344                 return mContext.getDrawable(R.drawable.direction_uturn_left);
345             case STRAIGHT:
346                 return mContext.getDrawable(R.drawable.direction_continue);
347             case FERRY_BOAT:
348                 return mContext.getDrawable(R.drawable.direction_close);
349             case FERRY_TRAIN:
350                 return mContext.getDrawable(R.drawable.direction_close);
351             case DESTINATION:
352                 return mContext.getDrawable(R.drawable.direction_arrive);
353             case DESTINATION_STRAIGHT:
354                 return mContext.getDrawable(R.drawable.direction_arrive_straight);
355             case DESTINATION_LEFT:
356                 return mContext.getDrawable(R.drawable.direction_arrive_left);
357             case DESTINATION_RIGHT:
358                 return mContext.getDrawable(R.drawable.direction_arrive_right);
359         }
360         return null;
361     }
362 
formatDistance(@ullable Distance distance)363     private String formatDistance(@Nullable Distance distance) {
364         if (distance == null || distance.getDisplayUnits() == Distance.Unit.UNKNOWN) {
365             return null;
366         }
367 
368         String unit = "";
369 
370         switch (distance.getDisplayUnits()) {
371             case METERS:
372                 unit = "m";
373                 break;
374             case KILOMETERS:
375                 unit = "km";
376                 break;
377             case MILES:
378                 unit = "mi";
379                 break;
380             case YARDS:
381                 unit = "yd";
382                 break;
383             case FEET:
384                 unit = "ft";
385                 break;
386         }
387         return String.format("In %s %s", distance.getDisplayValue(), unit);
388     }
389 }
390