1 /* 2 * Copyright (C) 2021 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 com.android.car.cluster.view; 17 18 import android.car.cluster.navigation.NavigationState.Cue; 19 import android.car.cluster.navigation.NavigationState.Cue.CueElement; 20 import android.car.cluster.navigation.NavigationState.ImageReference; 21 import android.content.Context; 22 import android.graphics.Bitmap; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.os.Handler; 25 import android.text.SpannableStringBuilder; 26 import android.text.style.ImageSpan; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.widget.TextView; 30 31 import com.android.car.cluster.osdouble.R; 32 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.concurrent.CompletableFuture; 38 import java.util.stream.Collectors; 39 40 /** 41 * View component that displays the Cue information on the instrument cluster display 42 */ 43 public class CueView extends TextView { 44 private static final String TAG = "Cluster.CueView"; 45 46 private String mImageSpanText; 47 private CompletableFuture<?> mFuture; 48 private Handler mHandler = new Handler(); 49 private Cue mContent; 50 CueView(Context context)51 public CueView(Context context) { 52 super(context); 53 init(context); 54 } 55 CueView(Context context, AttributeSet attrs)56 public CueView(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 init(context); 59 } 60 CueView(Context context, AttributeSet attrs, int defStyle)61 public CueView(Context context, AttributeSet attrs, int defStyle) { 62 super(context, attrs, defStyle); 63 init(context); 64 } 65 init(Context context)66 private void init(Context context) { 67 mImageSpanText = context.getString(R.string.span_image); 68 } 69 70 /** 71 * Set the given {@link Cue} to the View. 72 * @param cue {@link Cue} to set in the view 73 * @param imageResolver {@link ImageResolver} to fetch the {@link Bitmap} 74 * @param alpha the opacity of the image 75 */ setCue(Cue cue, ImageResolver imageResolver, float alpha)76 public void setCue(Cue cue, ImageResolver imageResolver, float alpha) { 77 if (cue == null) { 78 setText(null); 79 return; 80 } 81 82 if (mFuture != null && !Objects.equals(cue, mContent)) { 83 mFuture.cancel(true); 84 } 85 86 List<ImageReference> imageReferences = cue.getElementsList().stream() 87 .filter(element -> element.hasImage()) 88 .map(element -> element.getImage()) 89 .collect(Collectors.toList()); 90 mFuture = imageResolver 91 .getBitmaps(imageReferences, 0, getLineHeight()) 92 .thenAccept(bitmaps -> { 93 mHandler.post(() -> update(cue, bitmaps, alpha)); 94 mFuture = null; 95 }) 96 .exceptionally(ex -> { 97 if (Log.isLoggable(TAG, Log.DEBUG)) { 98 Log.d(TAG, "Unable to fetch images for cue: " + cue); 99 } 100 mHandler.post( 101 () -> update(cue, Collections.emptyMap(), alpha)); 102 return null; 103 }); 104 mContent = cue; 105 } 106 update(Cue cue, Map<ImageReference, Bitmap> bitmaps, float alpha)107 private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps, float alpha) { 108 SpannableStringBuilder builder = new SpannableStringBuilder(); 109 110 for (CueElement element : cue.getElementsList()) { 111 if (element.hasImage()) { 112 Bitmap bitmap = bitmaps.get(element.getImage()); 113 if (bitmap != null) { 114 String imageText = element.getText().isEmpty() ? mImageSpanText : 115 element.getText(); 116 appendImage(builder, imageText, bitmap, alpha); 117 } 118 } else if (!element.getText().isEmpty()) { 119 builder.append(element.getText()); 120 } 121 } 122 123 setText(builder); 124 setAlpha(alpha); 125 } 126 appendImage(SpannableStringBuilder builder, String text, Bitmap bitmap, float alpha)127 private void appendImage(SpannableStringBuilder builder, String text, Bitmap bitmap, 128 float alpha) { 129 int start = builder.length(); 130 int end = start + text.length(); 131 builder.append(text); 132 BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); 133 drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); 134 drawable.setAlpha((int) (alpha * 255)); 135 builder.setSpan(new ImageSpan(drawable), start, end, 0); 136 } 137 } 138