• 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 android.annotation.Nullable;
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.PorterDuff;
23 import android.graphics.PorterDuffColorFilter;
24 import android.graphics.drawable.Drawable;
25 import android.graphics.drawable.VectorDrawable;
26 import android.os.Handler;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 
32 import android.car.cluster.navigation.NavigationState.ImageReference;
33 import android.car.cluster.navigation.NavigationState.Lane;
34 import android.car.cluster.navigation.NavigationState.Lane.LaneDirection;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * View component that displays the Lane preview information on the instrument cluster display
41  */
42 public class LaneView extends LinearLayout {
43     private static final String TAG = "Cluster.LaneView";
44 
45     private Handler mHandler = new Handler();
46 
47     private ArrayList<Lane> mLanes;
48 
49     private final int mWidth = (int) getResources().getDimension(R.dimen.lane_width);
50     private final int mHeight = (int) getResources().getDimension(R.dimen.lane_height);
51     private final int mOffset = (int) getResources().getDimension(R.dimen.lane_icon_offset);
52 
53     private enum Shift {
54         LEFT,
55         RIGHT,
56         BOTH
57     }
58 
LaneView(Context context)59     public LaneView(Context context) {
60         super(context);
61     }
62 
LaneView(Context context, AttributeSet attrs)63     public LaneView(Context context, AttributeSet attrs) {
64         super(context, attrs);
65     }
66 
LaneView(Context context, AttributeSet attrs, int defStyleAttr)67     public LaneView(Context context, AttributeSet attrs, int defStyleAttr) {
68         super(context, attrs, defStyleAttr);
69     }
70 
setLanes(ImageReference imageReference, ImageResolver imageResolver)71     public void setLanes(ImageReference imageReference, ImageResolver imageResolver) {
72         imageResolver
73                 .getBitmap(imageReference, 0, getHeight(), 0.5f)
74                 .thenAccept(bitmap -> {
75                     mHandler.post(() -> {
76                         removeAllViews();
77                         ImageView imgView = new ImageView(getContext());
78                         imgView.setImageBitmap(bitmap);
79                         imgView.setAdjustViewBounds(true);
80                         addView(imgView);
81                     });
82                 })
83                 .exceptionally(ex -> {
84                     removeAllViews();
85                     if (Log.isLoggable(TAG, Log.DEBUG)) {
86                         Log.d(TAG, "Unable to fetch image for lane: " + imageReference);
87                     }
88                     return null;
89                 });
90     }
91 
setLanes(List<Lane> lanes, float alpha)92     public void setLanes(List<Lane> lanes, float alpha) {
93         mLanes = new ArrayList<>(lanes);
94         removeAllViews();
95 
96         // Use drawables for lane directional guidance
97         for (Lane lane : mLanes) {
98             Bitmap bitmap = combineBitmapFromLane(lane);
99             ImageView imgView = new ImageView(getContext());
100             imgView.setImageBitmap(bitmap);
101             imgView.setAdjustViewBounds(true);
102             imgView.setImageAlpha((int) (alpha * 255));
103             addView(imgView);
104         }
105     }
106 
combineBitmapFromLane(Lane lane)107     private Bitmap combineBitmapFromLane(Lane lane) {
108         if (lane.getLaneDirectionsList().isEmpty()) {
109             return null;
110         }
111 
112         Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
113         Canvas canvas = new Canvas(bitmap);
114 
115         Shift shift = getShift(lane);
116 
117         for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
118             if (!laneDir.getIsHighlighted()) {
119                 drawToCanvas(laneDir, canvas, false, shift);
120             }
121         }
122 
123         for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
124             if (laneDir.getIsHighlighted()) {
125                 drawToCanvas(laneDir, canvas, true, shift);
126             }
127         }
128 
129         return bitmap;
130     }
131 
drawToCanvas(LaneDirection laneDir, Canvas canvas, boolean isHighlighted, Shift shift)132     private void drawToCanvas(LaneDirection laneDir, Canvas canvas, boolean isHighlighted,
133             Shift shift) {
134         int offset = getOffset(laneDir, shift);
135         VectorDrawable icon = (VectorDrawable) getLaneIcon(laneDir);
136         icon.setBounds(offset, 0, mWidth + offset, mHeight);
137         icon.setColorFilter(new PorterDuffColorFilter(isHighlighted
138                 ? getContext().getColor(R.color.laneDirectionHighlighted)
139                 : getContext().getColor(R.color.laneDirection),
140                 PorterDuff.Mode.SRC_ATOP));
141         icon.draw(canvas);
142     }
143 
144     /**
145      * Determines the offset direction to line up overlapping lane directions.
146      */
getShift(Lane lane)147     private Shift getShift(Lane lane) {
148         boolean containsRight = false;
149         boolean containsLeft = false;
150         boolean containsStraight = false;
151 
152         for (LaneDirection laneDir : lane.getLaneDirectionsList()) {
153             if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_RIGHT)
154                     || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_RIGHT)
155                     || laneDir.getShape().equals(LaneDirection.Shape.SHARP_RIGHT)
156                     || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_RIGHT)) {
157                 containsRight = true;
158             }
159             if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_LEFT)
160                     || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_LEFT)
161                     || laneDir.getShape().equals(LaneDirection.Shape.SHARP_LEFT)
162                     || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_LEFT)) {
163                 containsLeft = true;
164             }
165             if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) {
166                 containsStraight = true;
167             }
168         }
169 
170         if (containsLeft && containsRight) {
171             //shift turns outwards
172             return Shift.BOTH;
173         } else if (containsStraight && containsRight) {
174             //shift straight lane dir to the left
175             return Shift.LEFT;
176         } else if (containsStraight && containsLeft) {
177             //shift straight lane dir to the right
178             return Shift.RIGHT;
179         }
180 
181         return null;
182     }
183 
184     /**
185      * Returns the offset value of the lane direction based on the given shift direction.
186      */
getOffset(LaneDirection laneDir, Shift shift)187     private int getOffset(LaneDirection laneDir, Shift shift) {
188         if (shift == Shift.BOTH) {
189             if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_LEFT)
190                     || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_LEFT)
191                     || laneDir.getShape().equals(LaneDirection.Shape.SHARP_LEFT)
192                     || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_LEFT)) {
193                 return -mOffset;
194             }
195             if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_RIGHT)
196                     || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_RIGHT)
197                     || laneDir.getShape().equals(LaneDirection.Shape.SHARP_RIGHT)
198                     || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_RIGHT)) {
199                 return mOffset;
200             }
201         } else if (shift == Shift.LEFT) {
202             if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) {
203                 return -mOffset;
204             }
205         } else if (shift == Shift.RIGHT) {
206             if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) {
207                 return mOffset;
208             }
209         }
210 
211         return 0;
212     }
213 
getLaneIcon(@ullable LaneDirection laneDir)214     private Drawable getLaneIcon(@Nullable LaneDirection laneDir) {
215         if (laneDir == null) {
216             return null;
217         }
218         switch (laneDir.getShape()) {
219             case UNKNOWN:
220                 return null;
221             case STRAIGHT:
222                 return mContext.getDrawable(R.drawable.direction_continue);
223             case SLIGHT_LEFT:
224                 return mContext.getDrawable(R.drawable.direction_turn_slight_left);
225             case SLIGHT_RIGHT:
226                 return mContext.getDrawable(R.drawable.direction_turn_slight_right);
227             case NORMAL_LEFT:
228                 return mContext.getDrawable(R.drawable.direction_turn_left);
229             case NORMAL_RIGHT:
230                 return mContext.getDrawable(R.drawable.direction_turn_right);
231             case SHARP_LEFT:
232                 return mContext.getDrawable(R.drawable.direction_turn_sharp_left);
233             case SHARP_RIGHT:
234                 return mContext.getDrawable(R.drawable.direction_turn_sharp_right);
235             case U_TURN_LEFT:
236                 return mContext.getDrawable(R.drawable.direction_uturn_left);
237             case U_TURN_RIGHT:
238                 return mContext.getDrawable(R.drawable.direction_uturn_right);
239         }
240         return null;
241     }
242 
243     @Override
setAlpha(float alpha)244     public void setAlpha(float alpha) {
245         super.setAlpha(alpha);
246 
247     }
248 }
249