Telegram - show images on map

This commit is contained in:
crimean 2018-06-14 21:01:10 +03:00
parent 8189ca95d5
commit 0d89b958ce
12 changed files with 285 additions and 30 deletions

View file

@ -42,6 +42,17 @@
</service>
<receiver android:name=".OnTelegramServiceAlarmReceiver" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="net.osmand.telegram.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
</provider>
</application>
</manifest>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="share"
path="share"/>
<external-files-path
name="files"
path="/"/>
<external-path
name="external_files"
path="." />
<files-path
name="files_path"
path="." />
<external-cache-path
name="external_cache_path"
path="." />
</paths>

View file

@ -6,9 +6,14 @@ import android.os.Parcelable;
import net.osmand.aidl.map.ALatLon;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AMapPoint implements Parcelable {
public static final int POINT_IMAGE_SIZE_PX = 160;
public static final String POINT_IMAGE_URI_PARAM = "point_image_uri_param";
private String id;
private String shortName;
private String fullName;
@ -16,9 +21,10 @@ public class AMapPoint implements Parcelable {
private int color;
private ALatLon location;
private List<String> details = new ArrayList<>();
private Map<String, String> params = new HashMap<>();
public AMapPoint(String id, String shortName, String fullName, String typeName, int color,
ALatLon location, List<String> details) {
ALatLon location, List<String> details, Map<String, String> params) {
this.id = id;
this.shortName = shortName;
this.fullName = fullName;
@ -28,6 +34,9 @@ public class AMapPoint implements Parcelable {
if (details != null) {
this.details.addAll(details);
}
if (params != null) {
this.params.putAll(params);
}
}
public AMapPoint(Parcel in) {
@ -73,6 +82,10 @@ public class AMapPoint implements Parcelable {
return details;
}
public Map<String, String> getParams() {
return params;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(id);
out.writeString(shortName);
@ -81,6 +94,7 @@ public class AMapPoint implements Parcelable {
out.writeInt(color);
out.writeParcelable(location, flags);
out.writeStringList(details);
out.writeMap(params);
}
private void readFromParcel(Parcel in) {
@ -91,6 +105,7 @@ public class AMapPoint implements Parcelable {
color = in.readInt();
location = in.readParcelable(ALatLon.class.getClassLoader());
in.readStringList(details);
in.readMap(params, HashMap.class.getClassLoader());
}
public int describeContents() {

View file

@ -172,6 +172,10 @@ class MainActivity : AppCompatActivity(), TelegramListener {
}
}
override fun onTelegramUserChanged(user: TdApi.User) {
}
override fun onTelegramError(code: Int, message: String) {
runOnUi {
Toast.makeText(this@MainActivity, "$code - $message", Toast.LENGTH_LONG).show()

View file

@ -8,7 +8,6 @@ import android.content.ServiceConnection
import android.net.Uri
import android.os.IBinder
import android.os.RemoteException
import android.widget.Toast
import net.osmand.aidl.IOsmAndAidlInterface
import net.osmand.aidl.favorite.AFavorite
import net.osmand.aidl.favorite.AddFavoriteParams
@ -537,10 +536,10 @@ class OsmandAidlHelper(private val app: Application) {
* @param details - list of details. Displayed under context menu.
*/
fun addMapPoint(layerId: String, pointId: String, shortName: String, fullName: String,
typeName: String, color: Int, location: ALatLon, details: List<String>?): Boolean {
typeName: String, color: Int, location: ALatLon, details: List<String>?, params: Map<String, String>?): Boolean {
if (mIOsmAndAidlInterface != null) {
try {
val point = AMapPoint(pointId, shortName, fullName, typeName, color, location, details)
val point = AMapPoint(pointId, shortName, fullName, typeName, color, location, details, params)
return mIOsmAndAidlInterface!!.addMapPoint(AddMapPointParams(layerId, point))
} catch (e: RemoteException) {
e.printStackTrace()
@ -563,10 +562,10 @@ class OsmandAidlHelper(private val app: Application) {
* @param details - list of details. Displayed under context menu.
*/
fun updateMapPoint(layerId: String, pointId: String, shortName: String, fullName: String,
typeName: String, color: Int, location: ALatLon, details: List<String>?): Boolean {
typeName: String, color: Int, location: ALatLon, details: List<String>?, params: Map<String, String>?): Boolean {
if (mIOsmAndAidlInterface != null) {
try {
val point = AMapPoint(pointId, shortName, fullName, typeName, color, location, details)
val point = AMapPoint(pointId, shortName, fullName, typeName, color, location, details, params)
return mIOsmAndAidlInterface!!.updateMapPoint(UpdateMapPointParams(layerId, point))
} catch (e: RemoteException) {
e.printStackTrace()

View file

@ -1,9 +1,15 @@
package net.osmand.telegram.helpers
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.text.TextUtils
import net.osmand.aidl.map.ALatLon
import net.osmand.aidl.maplayer.point.AMapPoint
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.utils.AndroidUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.io.File
class ShowLocationHelper(private val app: TelegramApplication) {
@ -26,6 +32,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val content = message.content
if (content is TdApi.MessageLocation) {
var userName = ""
var photoUri: Uri? = null
val user = telegramHelper.getUser(message.senderUserId)
if (user != null) {
userName = "${user.firstName} ${user.lastName}".trim()
@ -35,13 +42,22 @@ class ShowLocationHelper(private val app: TelegramApplication) {
if (userName.isEmpty()) {
userName = user.phoneNumber
}
val photoPath = telegramHelper.getUserPhotoPath(user)
if (!TextUtils.isEmpty(photoPath)) {
photoUri = AndroidUtils.getUriForFile(app, File(photoPath))
app.grantUriPermission("net.osmand.plus", photoUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
if (userName.isEmpty()) {
userName = message.senderUserId.toString()
}
setupMapLayer()
val params = mutableMapOf<String, String>()
if (photoUri != null) {
params[AMapPoint.POINT_IMAGE_URI_PARAM] = photoUri.toString()
}
osmandHelper.addMapPoint(MAP_LAYER_ID, "${chatTitle}_${message.senderUserId}", userName, userName,
chatTitle, Color.RED, ALatLon(content.location.latitude, content.location.longitude), null)
chatTitle, Color.RED, ALatLon(content.location.latitude, content.location.longitude), null, params)
}
} else if (osmandHelper.isOsmandBound()) {
osmandHelper.connectOsmand()

View file

@ -42,6 +42,7 @@ class TelegramHelper private constructor() {
private val chatLiveMessages = ConcurrentHashMap<Long, Long>()
private val downloadChatFilesMap = ConcurrentHashMap<String, TdApi.Chat>()
private val downloadUserFilesMap = ConcurrentHashMap<String, TdApi.User>()
private val usersLiveMessages = ConcurrentHashMap<Long, TdApi.Message>()
@ -140,6 +141,7 @@ class TelegramHelper private constructor() {
fun onTelegramChatsRead()
fun onTelegramChatsChanged()
fun onTelegramChatChanged(chat: TdApi.Chat)
fun onTelegramUserChanged(user: TdApi.User)
fun onTelegramError(code: Int, message: String)
fun onSendLiveLicationError(code: Int, message: String)
}
@ -215,6 +217,54 @@ class TelegramHelper private constructor() {
return client != null && haveAuthorization
}
fun getUserPhotoPath(user: TdApi.User):String? {
return if (hasLocalUserPhoto(user)) {
user.profilePhoto?.small?.local?.path
} else {
if (hasRemoteUserPhoto(user)) {
requestUserPhoto(user)
}
null
}
}
private fun hasLocalUserPhoto(user: TdApi.User): Boolean {
val localPhoto = user.profilePhoto?.small?.local
return if (localPhoto != null) {
localPhoto.canBeDownloaded && localPhoto.isDownloadingCompleted && localPhoto.path.isNotEmpty()
} else {
false
}
}
private fun hasRemoteUserPhoto(user: TdApi.User): Boolean {
val remotePhoto = user.profilePhoto?.small?.remote
return remotePhoto?.id?.isNotEmpty() ?: false
}
private fun requestUserPhoto(user: TdApi.User) {
val remotePhoto = user.profilePhoto?.small?.remote
if (remotePhoto != null && remotePhoto.id.isNotEmpty()) {
downloadUserFilesMap[remotePhoto.id] = user
client!!.send(TdApi.GetRemoteFile(remotePhoto.id, null)) { obj ->
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
val code = error.code
if (code != IGNORED_ERROR_CODE) {
listener?.onTelegramError(code, error.message)
}
}
TdApi.File.CONSTRUCTOR -> {
val file = obj as TdApi.File
client!!.send(TdApi.DownloadFile(file.id, 10), defaultHandler)
}
else -> listener?.onTelegramError(-1, "Receive wrong response from TDLib: $obj")
}
}
}
}
private fun requestChats(reload: Boolean = false) {
synchronized(chatList) {
if (reload) {
@ -776,8 +826,17 @@ class TelegramHelper private constructor() {
if (chat != null) {
synchronized(chat) {
chat.photo?.small = updateFile.file
listener?.onTelegramChatChanged(chat)
}
listener?.onTelegramChatChanged(chat)
return
}
val user = downloadUserFilesMap.remove(remoteId)
if (user != null) {
synchronized(user) {
user.profilePhoto?.small = updateFile.file
}
listener?.onTelegramUserChanged(user)
return
}
}
}

View file

@ -5,14 +5,17 @@ import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.support.annotation.AttrRes
import android.support.annotation.ColorInt
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v4.content.FileProvider
import android.util.TypedValue
import android.util.TypedValue.COMPLEX_UNIT_DIP
import android.view.View
import android.view.inputmethod.InputMethodManager
import java.io.File
object AndroidUtils {
@ -61,4 +64,12 @@ object AndroidUtils {
ta.recycle()
return color
}
fun getUriForFile(context: Context, file: File): Uri {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
Uri.fromFile(file)
} else {
FileProvider.getUriForFile(context, "net.osmand.telegram.fileprovider", file)
}
}
}

View file

@ -93,7 +93,7 @@ class UiUtils(private val app: TelegramApplication) {
return getDrawable(id, if (light) R.color.icon_color_light else 0)
}
fun createCircleBitmap(source: Bitmap, recycleSource: Boolean = false): Bitmap {
private fun createCircleBitmap(source: Bitmap, recycleSource: Boolean = false): Bitmap {
val size = Math.min(source.width, source.height)
val width = (source.width - size) / 2

View file

@ -7,7 +7,9 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.drawable.ClipDrawable;
@ -99,6 +101,20 @@ public class AndroidUtils {
}
public static Bitmap scaleBitmap(Bitmap bm, int newWidth, int newHeight, boolean keepOriginalBitmap) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
if (!keepOriginalBitmap) {
bm.recycle();
}
return resizedBitmap;
}
public static ColorStateList createBottomNavColorStateList(Context ctx, boolean nightMode) {
return AndroidUtils.createCheckedColorStateList(ctx, nightMode,
R.color.icon_color, R.color.wikivoyage_active_light,
@ -130,7 +146,7 @@ public class AndroidUtils {
while (i < text.length() && i != -1) {
ImageSpan span = new ImageSpan(icon) {
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
float x, int top, int y, int bottom, Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
int transY = bottom - drawable.getBounds().bottom;
@ -197,7 +213,7 @@ public class AndroidUtils {
ViewParent viewParent = view.getParent();
while (viewParent != null && viewParent instanceof View) {
View parentView = (View)viewParent;
View parentView = (View) viewParent;
if (parentView.getId() == id)
return parentView;

View file

@ -6,9 +6,14 @@ import android.os.Parcelable;
import net.osmand.aidl.map.ALatLon;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AMapPoint implements Parcelable {
public static final int POINT_IMAGE_SIZE_PX = 160;
public static final String POINT_IMAGE_URI_PARAM = "point_image_uri_param";
private String id;
private String shortName;
private String fullName;
@ -16,9 +21,10 @@ public class AMapPoint implements Parcelable {
private int color;
private ALatLon location;
private List<String> details = new ArrayList<>();
private Map<String, String> params = new HashMap<>();
public AMapPoint(String id, String shortName, String fullName, String typeName, int color,
ALatLon location, List<String> details) {
ALatLon location, List<String> details, Map<String, String> params) {
this.id = id;
this.shortName = shortName;
this.fullName = fullName;
@ -28,6 +34,9 @@ public class AMapPoint implements Parcelable {
if (details != null) {
this.details.addAll(details);
}
if (params != null) {
this.params.putAll(params);
}
}
public AMapPoint(Parcel in) {
@ -73,6 +82,10 @@ public class AMapPoint implements Parcelable {
return details;
}
public Map<String, String> getParams() {
return params;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(id);
out.writeString(shortName);
@ -81,6 +94,7 @@ public class AMapPoint implements Parcelable {
out.writeInt(color);
out.writeParcelable(location, flags);
out.writeStringList(details);
out.writeMap(params);
}
private void readFromParcel(Parcel in) {
@ -91,6 +105,7 @@ public class AMapPoint implements Parcelable {
color = in.readInt();
location = in.readParcelable(ALatLon.class.getClassLoader());
in.readStringList(details);
in.readMap(params, HashMap.class.getClassLoader());
}
public int describeContents() {

View file

@ -1,13 +1,16 @@
package net.osmand.plus.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
import net.osmand.AndroidUtils;
import net.osmand.aidl.map.ALatLon;
import net.osmand.aidl.maplayer.AMapLayer;
import net.osmand.aidl.maplayer.point.AMapPoint;
@ -17,8 +20,18 @@ import net.osmand.data.RotatedTileBox;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static net.osmand.aidl.maplayer.point.AMapPoint.POINT_IMAGE_SIZE_PX;
public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider {
private static int POINT_OUTER_COLOR = 0x88555555;
@ -29,9 +42,12 @@ public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider
private OsmandMapTileView view;
private Paint pointInnerCircle;
private Paint pointOuter;
private Paint bitmapPaint;
private final static float startZoom = 7;
private Paint paintTextIcon;
private Map<String, Bitmap> pointImages = new ConcurrentHashMap<>();
public AidlMapLayer(MapActivity map, AMapLayer aidlLayer) {
this.map = map;
this.aidlLayer = aidlLayer;
@ -40,9 +56,6 @@ public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider
@Override
public void initLayer(OsmandMapTileView view) {
this.view = view;
DisplayMetrics dm = new DisplayMetrics();
WindowManager wmgr = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
wmgr.getDefaultDisplay().getMetrics(dm);
pointInnerCircle = new Paint();
pointInnerCircle.setColor(view.getApplication().getResources().getColor(R.color.poi_background));
@ -60,6 +73,11 @@ public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider
pointOuter.setColor(POINT_OUTER_COLOR);
pointOuter.setAntiAlias(true);
pointOuter.setStyle(Paint.Style.FILL_AND_STROKE);
bitmapPaint = new Paint();
bitmapPaint.setAntiAlias(true);
bitmapPaint.setDither(true);
bitmapPaint.setFilterBitmap(true);
}
private int getRadiusPoi(RotatedTileBox tb) {
@ -78,24 +96,48 @@ public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider
}
@Override
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) {
final int r = getRadiusPoi(tileBox);
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
}
@Override
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
float density = (float) Math.ceil(tileBox.getDensity());
final int radius = getRadiusPoi(tileBox);
final int maxRadius = (int) (Math.max(radius, POINT_IMAGE_SIZE_PX) + density);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
paintTextIcon.setTextSize(radius * 3 / 2);
Set<String> imageRequests = new HashSet<>();
List<AMapPoint> points = aidlLayer.getPoints();
for (AMapPoint point : points) {
ALatLon l = point.getLocation();
if (l != null) {
int x = (int) tileBox.getPixXFromLatLon(l.getLatitude(), l.getLongitude());
int y = (int) tileBox.getPixYFromLatLon(l.getLatitude(), l.getLongitude());
pointInnerCircle.setColor(point.getColor());
pointOuter.setColor(POINT_OUTER_COLOR);
paintTextIcon.setColor(PAINT_TEXT_ICON_COLOR);
canvas.drawCircle(x, y, r + (float)Math.ceil(tileBox.getDensity()), pointOuter);
canvas.drawCircle(x, y, r - (float)Math.ceil(tileBox.getDensity()), pointInnerCircle);
paintTextIcon.setTextSize(r * 3 / 2);
canvas.drawText(point.getShortName(), x, y + r * 2.5f, paintTextIcon);
if (tileBox.containsPoint(x, y, maxRadius)) {
Map<String, String> params = point.getParams();
String imageUriStr = params.get(AMapPoint.POINT_IMAGE_URI_PARAM);
if (!TextUtils.isEmpty(imageUriStr)) {
Bitmap bitmap = pointImages.get(imageUriStr);
if (bitmap == null) {
imageRequests.add(imageUriStr);
} else {
canvas.drawBitmap(bitmap, x - bitmap.getHeight() / 2, y - bitmap.getWidth() / 2, bitmapPaint);
canvas.drawText(point.getShortName(), x, y + maxRadius * 0.9f, paintTextIcon);
}
} else {
pointInnerCircle.setColor(point.getColor());
pointOuter.setColor(POINT_OUTER_COLOR);
canvas.drawCircle(x, y, radius + density, pointOuter);
canvas.drawCircle(x, y, radius - density, pointInnerCircle);
canvas.drawText(point.getShortName(), x, y + radius * 2.5f, paintTextIcon);
}
}
}
}
if (imageRequests.size() > 0) {
executeTaskInBackground(new PointImageReaderTask(this), imageRequests.toArray(new String[imageRequests.size()]));
}
}
@Override
@ -178,4 +220,47 @@ public class AidlMapLayer extends OsmandMapLayer implements IContextMenuProvider
}
}
}
private static class PointImageReaderTask extends AsyncTask<String, Void, Void> {
private WeakReference<AidlMapLayer> layerRef;
private CropCircleTransformation circleTransformation = new CropCircleTransformation();
PointImageReaderTask(AidlMapLayer layer) {
this.layerRef = new WeakReference<>(layer);
}
@Override
protected Void doInBackground(String... imageUriStrs) {
for (String imageUriStr : imageUriStrs) {
Uri fileUri = Uri.parse(imageUriStr);
try {
AidlMapLayer layer = layerRef.get();
if (layer != null) {
try {
InputStream ims = layer.map.getContentResolver().openInputStream(fileUri);
if (ims != null) {
Bitmap bitmap = BitmapFactory.decodeStream(ims);
if (bitmap != null) {
bitmap = circleTransformation.transform(bitmap);
if (bitmap.getWidth() != POINT_IMAGE_SIZE_PX || bitmap.getHeight() != POINT_IMAGE_SIZE_PX) {
bitmap = AndroidUtils.scaleBitmap(bitmap, POINT_IMAGE_SIZE_PX, POINT_IMAGE_SIZE_PX, false);
}
layer.pointImages.put(imageUriStr, bitmap);
}
ims.close();
}
} catch (IOException e) {
// ignore
}
} else {
break;
}
} catch (Throwable e) {
// ignore
}
}
return null;
}
}
}