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