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