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()) 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)92 public void setLanes(List<Lane> lanes) { 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 addView(imgView); 103 } 104 } 105 combineBitmapFromLane(Lane lane)106 private Bitmap combineBitmapFromLane(Lane lane) { 107 if (lane.getLaneDirectionsList().isEmpty()) { 108 return null; 109 } 110 111 Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 112 Canvas canvas = new Canvas(bitmap); 113 114 Shift shift = getShift(lane); 115 116 for (LaneDirection laneDir : lane.getLaneDirectionsList()) { 117 if (!laneDir.getIsHighlighted()) { 118 drawToCanvas(laneDir, canvas, false, shift); 119 } 120 } 121 122 for (LaneDirection laneDir : lane.getLaneDirectionsList()) { 123 if (laneDir.getIsHighlighted()) { 124 drawToCanvas(laneDir, canvas, true, shift); 125 } 126 } 127 128 return bitmap; 129 } 130 drawToCanvas(LaneDirection laneDir, Canvas canvas, boolean isHighlighted, Shift shift)131 private void drawToCanvas(LaneDirection laneDir, Canvas canvas, boolean isHighlighted, 132 Shift shift) { 133 int offset = getOffset(laneDir, shift); 134 VectorDrawable icon = (VectorDrawable) getLaneIcon(laneDir); 135 icon.setBounds(offset, 0, mWidth + offset, mHeight); 136 icon.setColorFilter(new PorterDuffColorFilter(isHighlighted 137 ? getContext().getColor(R.color.laneDirectionHighlighted) 138 : getContext().getColor(R.color.laneDirection), 139 PorterDuff.Mode.SRC_ATOP)); 140 icon.draw(canvas); 141 } 142 143 /** 144 * Determines the offset direction to line up overlapping lane directions. 145 */ getShift(Lane lane)146 private Shift getShift(Lane lane) { 147 boolean containsRight = false; 148 boolean containsLeft = false; 149 boolean containsStraight = false; 150 151 for (LaneDirection laneDir : lane.getLaneDirectionsList()) { 152 if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_RIGHT) 153 || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_RIGHT) 154 || laneDir.getShape().equals(LaneDirection.Shape.SHARP_RIGHT) 155 || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_RIGHT)) { 156 containsRight = true; 157 } 158 if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_LEFT) 159 || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_LEFT) 160 || laneDir.getShape().equals(LaneDirection.Shape.SHARP_LEFT) 161 || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_LEFT)) { 162 containsLeft = true; 163 } 164 if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) { 165 containsStraight = true; 166 } 167 } 168 169 if (containsLeft && containsRight) { 170 //shift turns outwards 171 return Shift.BOTH; 172 } else if (containsStraight && containsRight) { 173 //shift straight lane dir to the left 174 return Shift.LEFT; 175 } else if (containsStraight && containsLeft) { 176 //shift straight lane dir to the right 177 return Shift.RIGHT; 178 } 179 180 return null; 181 } 182 183 /** 184 * Returns the offset value of the lane direction based on the given shift direction. 185 */ getOffset(LaneDirection laneDir, Shift shift)186 private int getOffset(LaneDirection laneDir, Shift shift) { 187 if (shift == Shift.BOTH) { 188 if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_LEFT) 189 || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_LEFT) 190 || laneDir.getShape().equals(LaneDirection.Shape.SHARP_LEFT) 191 || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_LEFT)) { 192 return -mOffset; 193 } 194 if (laneDir.getShape().equals(LaneDirection.Shape.NORMAL_RIGHT) 195 || laneDir.getShape().equals(LaneDirection.Shape.SLIGHT_RIGHT) 196 || laneDir.getShape().equals(LaneDirection.Shape.SHARP_RIGHT) 197 || laneDir.getShape().equals(LaneDirection.Shape.U_TURN_RIGHT)) { 198 return mOffset; 199 } 200 } else if (shift == Shift.LEFT) { 201 if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) { 202 return -mOffset; 203 } 204 } else if (shift == Shift.RIGHT) { 205 if (laneDir.getShape().equals(LaneDirection.Shape.STRAIGHT)) { 206 return mOffset; 207 } 208 } 209 210 return 0; 211 } 212 getLaneIcon(@ullable LaneDirection laneDir)213 private Drawable getLaneIcon(@Nullable LaneDirection laneDir) { 214 if (laneDir == null) { 215 return null; 216 } 217 switch (laneDir.getShape()) { 218 case UNKNOWN: 219 return null; 220 case STRAIGHT: 221 return mContext.getDrawable(R.drawable.direction_continue); 222 case SLIGHT_LEFT: 223 return mContext.getDrawable(R.drawable.direction_turn_slight_left); 224 case SLIGHT_RIGHT: 225 return mContext.getDrawable(R.drawable.direction_turn_slight_right); 226 case NORMAL_LEFT: 227 return mContext.getDrawable(R.drawable.direction_turn_left); 228 case NORMAL_RIGHT: 229 return mContext.getDrawable(R.drawable.direction_turn_right); 230 case SHARP_LEFT: 231 return mContext.getDrawable(R.drawable.direction_turn_sharp_left); 232 case SHARP_RIGHT: 233 return mContext.getDrawable(R.drawable.direction_turn_sharp_right); 234 case U_TURN_LEFT: 235 return mContext.getDrawable(R.drawable.direction_uturn_left); 236 case U_TURN_RIGHT: 237 return mContext.getDrawable(R.drawable.direction_uturn_right); 238 } 239 return null; 240 } 241 } 242