show emojis on map

This commit is contained in:
cyberta
2021-02-04 11:05:35 +01:00
parent 95b605e331
commit d68095eca7
6 changed files with 193 additions and 25 deletions
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.emoji;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
@@ -31,7 +32,7 @@ import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
class EmojiProvider {
public class EmojiProvider {
private static final String TAG = EmojiProvider.class.getSimpleName();
private static volatile EmojiProvider instance = null;
@@ -111,6 +112,19 @@ class EmojiProvider {
return getEmojiDrawable(drawInfo, false);
}
public boolean isEmoji(CharSequence emoji) {
return emojiTree.getEmoji(emoji, 0, emoji.length()) != null;
}
public @Nullable Bitmap getEmojiBitmap(CharSequence emoji, boolean background) {
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
EmojiDrawable drawable = ((EmojiDrawable) getEmojiDrawable(drawInfo, background));
if (drawable != null) {
return drawable.getEmojiBitmap();
}
return null;
}
protected @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo, boolean background) {
if (drawInfo == null) {
return null;
@@ -158,6 +172,26 @@ class EmojiProvider {
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
}
private Bitmap getEmojiBitmap() {
Bitmap singleEmoji = Bitmap.createBitmap((int) intrinsicWidth/2, (int) intrinsicHeight/2, Bitmap.Config.ARGB_8888);
final int row = info.getIndex() / EMOJI_PER_ROW;
final int rowIndex = info.getIndex() % EMOJI_PER_ROW;
Rect desRect = new Rect(0, 0, (int) intrinsicWidth, (int) intrinsicWidth);
Rect srcRect = new Rect((int)(rowIndex * intrinsicWidth),
(int)(row * intrinsicHeight + row * verticalPad)+1,
(int)(((rowIndex + 1) * intrinsicWidth)-1),
(int)((row + 1) * intrinsicHeight + row * verticalPad)-1);
Canvas canvas = new Canvas(singleEmoji);
canvas.scale(0.5f, 0.5f);
canvas.drawBitmap(bmp, srcRect, desRect, paint);
return singleEmoji;
}
@Override
public void draw(@NonNull Canvas canvas) {
if (bmp == null) {
@@ -7,10 +7,13 @@ import com.b44t.messenger.DcContext;
import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.map.model.MapSource;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.thoughtcrime.securesms.map.MapDataManager.TIMESTAMP_NOW;
@@ -20,10 +23,10 @@ import static org.thoughtcrime.securesms.map.MapDataManager.TIME_FRAME;
* Created by cyberta on 15.04.19.
*/
public class DataCollectionTask extends AsyncTask<Void, Void, Void> {
public class DataCollectionTask extends AsyncTask<Void, Void, Set<String>> {
public interface DataCollectionCallback {
void onDataCollectionFinished();
void onDataCollectionFinished(Set<String> emojiCodepoints);
}
private static final String TAG = DataCollectionTask.class.getSimpleName();
@@ -37,6 +40,7 @@ public class DataCollectionTask extends AsyncTask<Void, Void, Void> {
private final LatLngBounds.Builder boundingBuilder;
private final DcContext dcContext;
private final DataCollectionCallback callback;
private final EmojiProvider emojiProvider;
public DataCollectionTask(DcContext context,
int chatId,
@@ -45,6 +49,7 @@ public class DataCollectionTask extends AsyncTask<Void, Void, Void> {
ConcurrentHashMap featureCollections,
ConcurrentHashMap<Integer, Feature> lastPositions,
LatLngBounds.Builder boundingBuilder,
EmojiProvider emojiProvider,
DataCollectionCallback callback) {
this.chatId = chatId;
this.contactMapSources = contactMapSources;
@@ -54,6 +59,7 @@ public class DataCollectionTask extends AsyncTask<Void, Void, Void> {
this.dcContext = context;
this.callback = callback;
this.contactIds = contactIds;
this.emojiProvider = emojiProvider;
instances.add(this);
}
@@ -64,29 +70,32 @@ public class DataCollectionTask extends AsyncTask<Void, Void, Void> {
}
@Override
protected Void doInBackground(Void... voids) {
protected Set<String> doInBackground(Void... voids) {
Log.d(TAG, "performance test - collect Data start");
HashSet<String> emojiCodePoints = new HashSet<>();
DataCollector dataCollector = new DataCollector(dcContext,
contactMapSources,
featureCollections,
lastPositions,
boundingBuilder);
contactMapSources,
featureCollections,
lastPositions,
emojiCodePoints,
emojiProvider,
boundingBuilder);
for (int contactId : contactIds) {
dataCollector.updateSource(chatId,
contactId,
System.currentTimeMillis() - TIME_FRAME,
TIMESTAMP_NOW);
contactId,
System.currentTimeMillis() - TIME_FRAME,
TIMESTAMP_NOW);
if (this.isCancelled()) {
break;
}
}
return null;
return emojiCodePoints;
}
@Override
protected void onPostExecute(Void value) {
protected void onPostExecute(Set<String> strings) {
if (!this.isCancelled()) {
callback.onDataCollectionFinished();
callback.onDataCollectionFinished(strings);
}
instances.remove(this);
Log.d(TAG, "performance test - collect Data finished");
@@ -11,14 +11,17 @@ import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.map.model.MapSource;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.thoughtcrime.securesms.map.MapDataManager.ACCURACY;
import static org.thoughtcrime.securesms.map.MapDataManager.CONTACT_ID;
import static org.thoughtcrime.securesms.map.MapDataManager.IS_EMOJI_CHAR;
import static org.thoughtcrime.securesms.map.MapDataManager.IS_POI;
import static org.thoughtcrime.securesms.map.MapDataManager.LAST_LOCATION;
import static org.thoughtcrime.securesms.map.MapDataManager.LAST_POSITION_ICON;
@@ -41,18 +44,24 @@ public class DataCollector {
private ConcurrentHashMap<Integer, MapSource> contactMapSources;
private ConcurrentHashMap<String, LinkedList<Feature>> featureCollections;
private ConcurrentHashMap<Integer, Feature> lastPositions;
private Set<String> emojiCodePoints;
private LatLngBounds.Builder boundingBuilder;
private EmojiProvider emojiProvider;
public DataCollector(DcContext dcContext,
ConcurrentHashMap<Integer, MapSource> contactMapSources,
ConcurrentHashMap<String, LinkedList<Feature>> featureCollections,
ConcurrentHashMap<Integer, Feature> lastPositions,
Set<String> emojiCodePoints,
EmojiProvider emojiProvider,
LatLngBounds.Builder boundingBuilder) {
this.dcContext = dcContext;
this.contactMapSources = contactMapSources;
this.featureCollections = featureCollections;
this.lastPositions = lastPositions;
this.boundingBuilder = boundingBuilder;
this.emojiCodePoints = emojiCodePoints;
this.emojiProvider = emojiProvider;
}
@@ -85,6 +94,7 @@ public class DataCollector {
"";
boolean isPoi = locations.isIndependent(i);
int messageId = locations.getMsgId(i);
boolean isEmojiChar = !codepointChar.isEmpty() && emojiProvider.isEmoji(codepointChar);
Feature pointFeature = Feature.fromGeometry(point, new JsonObject(), String.valueOf(locations.getLocationId(i)));
pointFeature.addBooleanProperty(MARKER_SELECTED, false);
@@ -94,9 +104,17 @@ public class DataCollector {
pointFeature.addNumberProperty(MESSAGE_ID, messageId);
pointFeature.addNumberProperty(ACCURACY, locations.getAccuracy(i));
pointFeature.addStringProperty(MARKER_CHAR, codepointChar);
pointFeature.addStringProperty(MARKER_ICON, isPoi ?
contactMapMetadata.getMarkerPoi() :
contactMapMetadata.getMarkerIcon());
pointFeature.addBooleanProperty(IS_EMOJI_CHAR, isEmojiChar);
if (isPoi && isEmojiChar) {
//we save emoji bitmaps in mapboxstyle with the codepoint as the key
pointFeature.addStringProperty(MARKER_ICON, codepointChar);
emojiCodePoints.add(codepointChar);
} else if (isPoi) {
pointFeature.addStringProperty(MARKER_ICON, contactMapMetadata.getMarkerPoi());
} else {
pointFeature.addStringProperty(MARKER_ICON, contactMapMetadata.getMarkerIcon());
}
pointFeature.addBooleanProperty(IS_POI, isPoi);
if (isPoi && codepointChar.length() == 0 && messageId != 0) {
//has a long poi label
@@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.map;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.util.Pair;
import java.lang.ref.WeakReference;
public class EmojiBitmapGenerationTask extends AsyncTask<String, Pair<String, Bitmap>, Void> {
public interface EmojiBitmapGenerationCallback {
void onEmojiBitmapCreated(String codePoint, Bitmap emoji);
}
WeakReference<EmojiBitmapGenerationCallback> callbackWeakReference;
WeakReference<EmojiProvider> emojiProviderWeakReference;
public EmojiBitmapGenerationTask(EmojiBitmapGenerationCallback callback, EmojiProvider emojiProvider) {
callbackWeakReference = new WeakReference<>(callback);
emojiProviderWeakReference = new WeakReference<>(emojiProvider);
}
@Override
protected Void doInBackground(String... codePoints) {
for (String codePoint : codePoints) {
EmojiProvider emojiProvider = emojiProviderWeakReference.get();
if (emojiProvider == null) {
return null;
}
Bitmap emoji = emojiProvider.getEmojiBitmap(codePoint, true);
publishProgress(new Pair<>(codePoint, emoji));
}
return null;
}
@Override
protected void onProgressUpdate(Pair<String, Bitmap>... values) {
super.onProgressUpdate(values);
EmojiBitmapGenerationCallback callback = callbackWeakReference.get();
if (callback != null) {
callback.onEmojiBitmapCreated(values[0].first(), values[0].second());
}
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
@@ -2,9 +2,10 @@ package org.thoughtcrime.securesms.map;
import android.content.Context;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.annotation.NonNull;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcEventCenter;
import com.mapbox.android.core.permissions.PermissionsManager;
@@ -23,28 +24,33 @@ import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.map.DataCollectionTask.DataCollectionCallback;
import org.thoughtcrime.securesms.map.EmojiBitmapGenerationTask.EmojiBitmapGenerationCallback;
import org.thoughtcrime.securesms.map.GenerateInfoWindowTask.GenerateInfoWindowCallback;
import org.thoughtcrime.securesms.map.model.FilterProvider;
import org.thoughtcrime.securesms.map.model.MapSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.b44t.messenger.DcContext.DC_EVENT_LOCATION_CHANGED;
import static com.b44t.messenger.DcContext.DC_GCL_ADD_SELF;
import static com.mapbox.mapboxsdk.location.modes.RenderMode.COMPASS;
import static com.mapbox.mapboxsdk.style.expressions.Expression.all;
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.has;
import static com.mapbox.mapboxsdk.style.expressions.Expression.length;
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;
import static com.mapbox.mapboxsdk.style.expressions.Expression.neq;
import static com.mapbox.mapboxsdk.style.expressions.Expression.not;
import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase;
@@ -81,7 +87,10 @@ import static org.thoughtcrime.securesms.util.BitmapUtil.generateColoredBitmap;
* Created by cyberta on 07.03.19.
*/
public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateInfoWindowCallback, DataCollectionCallback {
public class MapDataManager implements DcEventCenter.DcEventDelegate,
GenerateInfoWindowCallback,
DataCollectionCallback,
EmojiBitmapGenerationCallback {
public static final String MARKER_SELECTED = "MARKER_SELECTED";
public static final String LAST_LOCATION = "LAST_LOCATION";
public static final String CONTACT_ID = "CONTACT_ID";
@@ -90,6 +99,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
public static final String MESSAGE_ID = "MESSAGE_ID";
public static final String ACCURACY = "ACCURACY";
public static final String MARKER_CHAR = "MARKER_CHAR";
public static final String IS_EMOJI_CHAR = "IS_EMOJI_CHAR";
public static final String POI_LONG_DESCRIPTION = "POI_LONG_DESCRIPTION";
public static final String MARKER_ICON = "MARKER_ICON";
public static final String IS_POI = "IS_POI";
@@ -109,6 +119,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
private ConcurrentHashMap<Integer, MapSource> contactMapSources = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, LinkedList<Feature>> featureCollections = new ConcurrentHashMap<>();
private ConcurrentHashMap<Integer, Feature> lastPositions = new ConcurrentHashMap<>();
private Set<String> emojiCodePoints = Collections.newSetFromMap(new ConcurrentHashMap<>());
private FilterProvider filterProvider = new FilterProvider();
private Feature selectedFeature;
private int chatId;
@@ -119,6 +130,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
private boolean isInitial = true;
private boolean showTraces = false;
private LocationComponent locationComponent;
private EmojiProvider emojiProvider;
public interface MapDataState {
void onDataInitialized(LatLngBounds bounds);
@@ -133,6 +145,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
boundingBuilder = new LatLngBounds.Builder();
this.callback = updateCallback;
this.locationComponent = locationComponent;
emojiProvider = EmojiProvider.getInstance(context);
initInfoWindowLayer();
initLastPositionLayer();
@@ -189,16 +202,21 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
Log.d(TAG, "updateEvent in MapDataManager called. eventId: " + eventId);
int contactId = event.getData1Int();
if (contactMapSources.containsKey(contactId)) {
HashSet<String> emojiCodePoints = new HashSet<>();
DataCollector collector = new DataCollector(dcContext,
contactMapSources,
featureCollections,
lastPositions, null);
contactMapSources,
featureCollections,
lastPositions,
emojiCodePoints,
emojiProvider,
null
);
collector.updateSource(chatId,
contactId,
System.currentTimeMillis() - TIME_FRAME,
TIMESTAMP_NOW);
refreshSource(contactId);
handleEmojiCodepoints(emojiCodePoints);
}
Log.d(TAG, "updateEvent in MapDataManager called. finished: " + eventId);
}
@@ -260,7 +278,13 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
}
@Override
public void onDataCollectionFinished() {
public void onEmojiBitmapCreated(String codePoint, Bitmap emoji) {
mapboxStyle.addImage(codePoint, emoji);
}
@Override
public void onDataCollectionFinished(Set <String> emojiCodePoints) {
handleEmojiCodepoints(emojiCodePoints);
for (MapSource source : contactMapSources.values()) {
initContactBasedLayers(source);
refreshSource(source.getContactId());
@@ -279,6 +303,16 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
}
}
public void handleEmojiCodepoints(Set<String> emojiCodePoints) {
// generate only new emoji bitmaps, so check the diff
emojiCodePoints.removeAll(this.emojiCodePoints);
if (!emojiCodePoints.isEmpty()) {
EmojiBitmapGenerationTask task = new EmojiBitmapGenerationTask(this, emojiProvider);
task.execute(emojiCodePoints.toArray(new String[0]));
this.emojiCodePoints.addAll(emojiCodePoints);
}
}
public void filterRange(long startTimestamp, long endTimestamp) {
int[] contactIds = getContactIds(chatId);
filterProvider.setRangeFilter(startTimestamp, endTimestamp);
@@ -511,7 +545,9 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
contactMapSources,
featureCollections,
lastPositions,
boundingBuilder,
emojiProvider,
this).execute();
}
@@ -523,6 +559,7 @@ public class MapDataManager implements DcEventCenter.DcEventDelegate, GenerateIn
featureCollections,
lastPositions,
boundingBuilder,
emojiProvider,
this).execute();
}
@@ -410,4 +410,18 @@ public class BitmapUtil {
return bitmap;
}
public static Bitmap scaleDown(Bitmap realImage, float maxImageSize,
boolean filter) {
float ratio = Math.min(
(float) maxImageSize / realImage.getWidth(),
(float) maxImageSize / realImage.getHeight());
int width = Math.round((float) ratio * realImage.getWidth());
int height = Math.round((float) ratio * realImage.getHeight());
Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, width,
height, filter);
return newBitmap;
}
}