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)69     public void setCue(Cue cue, ImageResolver imageResolver) {
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));
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(() -> update(cue, Collections.emptyMap()));
94                     return null;
95                 });
96         mContent = cue;
97     }
98 
update(Cue cue, Map<ImageReference, Bitmap> bitmaps)99     private void update(Cue cue, Map<ImageReference, Bitmap> bitmaps) {
100         SpannableStringBuilder builder = new SpannableStringBuilder();
101 
102         for (CueElement element : cue.getElementsList()) {
103             if (element.hasImage()) {
104                 Bitmap bitmap = bitmaps.get(element.getImage());
105                 if (bitmap != null) {
106                     String imageText = element.getText().isEmpty() ? mImageSpanText :
107                             element.getText();
108                     int start = builder.length();
109                     int end = start + imageText.length();
110                     builder.append(imageText);
111                     BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
112                     drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
113                     builder.setSpan(new ImageSpan(drawable), start, end, 0);
114                 }
115             } else if (!element.getText().isEmpty()) {
116                 builder.append(element.getText());
117             }
118         }
119 
120         setText(builder);
121     }
122 }
123