mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-27 19:15:50 +01:00
Zepp OS: Send notification pictures
This commit is contained in:
parent
4aa145560a
commit
9174d95894
@ -152,7 +152,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<ZeppOsSuppo
|
||||
return;
|
||||
}
|
||||
|
||||
fileTransferService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, this);
|
||||
fileTransferService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, false, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,6 +54,7 @@ public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<ZeppOsS
|
||||
"sport://file_transfer?appId=7073283073¶ms={}",
|
||||
"track_" + file.getTimestamp() + ".dat",
|
||||
fileBytes,
|
||||
false,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
@ -58,6 +60,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
|
||||
private int mVersion = -1;
|
||||
private int mChunkSize = -1;
|
||||
private int mCompressedChunkSize = -1;
|
||||
|
||||
public ZeppOsFileTransferService(final ZeppOsSupport support) {
|
||||
super(support, false);
|
||||
@ -81,13 +84,19 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
return;
|
||||
}
|
||||
mChunkSize = BLETypeConversions.toUint16(payload, 2);
|
||||
// TODO parse the rest for v3
|
||||
LOG.info("Got file transfer service: version={}, chunkSize={}", mVersion, mChunkSize);
|
||||
if (mVersion == 3) {
|
||||
// TODO parse the rest for v3
|
||||
mCompressedChunkSize = BLETypeConversions.toUint32(payload, 4);
|
||||
final TransactionBuilder builder = getSupport().createTransactionBuilder("enable file transfer v3 notifications");
|
||||
builder.notify(getSupport().getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3), true);
|
||||
builder.queue(getSupport().getQueue());
|
||||
}
|
||||
LOG.info(
|
||||
"Got file transfer service: version={}, chunkSize={}, compressedChunkSize={}",
|
||||
mVersion,
|
||||
mChunkSize,
|
||||
mCompressedChunkSize
|
||||
);
|
||||
return;
|
||||
case CMD_TRANSFER_REQUEST:
|
||||
handleFileTransferRequest(payload);
|
||||
@ -176,7 +185,14 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
|
||||
LOG.info("Got transfer request: session={}, url={}, filename={}, length={}, compressed={}", session, url, filename, length, compressed);
|
||||
|
||||
final FileTransferRequest request = new FileTransferRequest(url, filename, new byte[length], compressed, getSupport());
|
||||
final FileTransferRequest request = new FileTransferRequest(
|
||||
url,
|
||||
filename,
|
||||
new byte[length],
|
||||
compressed,
|
||||
compressed ? mCompressedChunkSize : mChunkSize,
|
||||
getSupport()
|
||||
);
|
||||
request.setCrc32(crc32);
|
||||
|
||||
if (mVersion < 3) {
|
||||
@ -260,6 +276,20 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] compress(final byte[] data) {
|
||||
final Deflater deflater = new Deflater();
|
||||
deflater.setInput(data);
|
||||
deflater.finish();
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length);
|
||||
final byte[] buf = new byte[8096];
|
||||
int read;
|
||||
while ((read = deflater.deflate(buf)) > 0) {
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] decompress(final byte[] data) {
|
||||
final Inflater inflater = new Inflater();
|
||||
final byte[] output = new byte[data.length];
|
||||
@ -276,16 +306,23 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void sendFile(final String url, final String filename, final byte[] bytes, final Callback callback) {
|
||||
public void sendFile(final String url, final String filename, final byte[] bytes, final boolean compress, final Callback callback) {
|
||||
if (mChunkSize < 0) {
|
||||
LOG.error("Service not initialized, refusing to send {}", url);
|
||||
callback.onFileUploadFinish(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Sending {} bytes to {}", bytes.length, url);
|
||||
LOG.info("Sending {} bytes to {} in {}", bytes.length, filename, url);
|
||||
|
||||
final FileTransferRequest request = new FileTransferRequest(url, filename, bytes, false, callback);
|
||||
final FileTransferRequest request = new FileTransferRequest(
|
||||
url,
|
||||
filename,
|
||||
bytes,
|
||||
compress && mCompressedChunkSize > 0,
|
||||
compress && mCompressedChunkSize > 0 ? mCompressedChunkSize : mChunkSize,
|
||||
callback
|
||||
);
|
||||
|
||||
if (mVersion == 3 && !mSessionRequests.isEmpty()) {
|
||||
// FIXME non-zero session on v3
|
||||
@ -302,6 +339,9 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
int payloadSize = 2 + url.length() + 1 + filename.length() + 1 + 4 + 4;
|
||||
if (mVersion == 3) {
|
||||
payloadSize += 2;
|
||||
if (compress) {
|
||||
payloadSize += 4;
|
||||
}
|
||||
}
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(payloadSize);
|
||||
@ -315,8 +355,10 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
buf.putInt(bytes.length);
|
||||
buf.putInt(request.getCrc32());
|
||||
if (mVersion == 3) {
|
||||
// compression ?
|
||||
buf.put((byte) 0);
|
||||
buf.put((byte) (compress ? 1 : 0));
|
||||
if (compress) {
|
||||
buf.putInt(mCompressedChunkSize);
|
||||
}
|
||||
buf.put((byte) 0);
|
||||
}
|
||||
|
||||
@ -333,7 +375,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
}
|
||||
|
||||
if (request.getProgress() >= request.getSize()) {
|
||||
LOG.info("Sending {} finished", request.getUrl());
|
||||
LOG.info("Finished sending {}", request.getUrl());
|
||||
onUploadFinish(session, true);
|
||||
return;
|
||||
}
|
||||
@ -354,7 +396,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
}
|
||||
|
||||
private void writeChunkV1(final FileTransferRequest request, final byte session) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(10 + mChunkSize);
|
||||
final ByteBuffer buf = ByteBuffer.allocate(10 + request.getChunkSize());
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.put(CMD_DATA_SEND);
|
||||
|
||||
@ -362,7 +404,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
if (request.getProgress() == 0) {
|
||||
flags |= FLAG_FIRST_CHUNK;
|
||||
}
|
||||
if (request.getProgress() + mChunkSize >= request.getSize()) {
|
||||
if (request.getProgress() + request.getChunkSize() >= request.getSize()) {
|
||||
flags |= FLAG_LAST_CHUNK;
|
||||
}
|
||||
|
||||
@ -379,7 +421,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
final byte[] payload = ArrayUtils.subarray(
|
||||
request.getBytes(),
|
||||
request.getProgress(),
|
||||
request.getProgress() + mChunkSize
|
||||
request.getProgress() + request.getChunkSize()
|
||||
);
|
||||
|
||||
buf.putShort((short) payload.length);
|
||||
@ -396,14 +438,14 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
final byte[] chunk = ArrayUtils.subarray(
|
||||
request.getBytes(),
|
||||
request.getProgress(),
|
||||
request.getProgress() + mChunkSize
|
||||
request.getProgress() + request.getChunkSize()
|
||||
);
|
||||
|
||||
byte flags = 0;
|
||||
if (request.getProgress() == 0) {
|
||||
flags |= FLAG_FIRST_CHUNK;
|
||||
}
|
||||
if (request.getProgress() + mChunkSize >= request.getSize()) {
|
||||
if (request.getProgress() + request.getChunkSize() >= request.getSize()) {
|
||||
flags |= FLAG_LAST_CHUNK;
|
||||
}
|
||||
|
||||
@ -494,16 +536,18 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
private final String filename;
|
||||
private final byte[] bytes;
|
||||
private final boolean compressed;
|
||||
private final int chunkSize;
|
||||
private final Callback callback;
|
||||
private int progress = 0;
|
||||
private byte index = 0;
|
||||
private int crc32;
|
||||
|
||||
public FileTransferRequest(final String url, final String filename, final byte[] bytes, boolean compressed, final Callback callback) {
|
||||
public FileTransferRequest(final String url, final String filename, final byte[] bytes, boolean compressed, int chunkSize, final Callback callback) {
|
||||
this.url = url;
|
||||
this.filename = filename;
|
||||
this.bytes = bytes;
|
||||
this.bytes = compressed ? compress(bytes) : bytes;
|
||||
this.compressed = compressed;
|
||||
this.chunkSize = chunkSize;
|
||||
this.callback = callback;
|
||||
this.crc32 = CheckSums.getCRC32(bytes);
|
||||
}
|
||||
@ -528,6 +572,10 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public int getChunkSize() {
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
public Callback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
@ -19,8 +19,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.servic
|
||||
import static org.apache.commons.lang3.ArrayUtils.subarray;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -29,6 +32,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
@ -42,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
@ -51,12 +56,16 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
|
||||
public static final short ENDPOINT = 0x001e;
|
||||
|
||||
public static final byte NOTIFICATION_CMD_CAPABILITIES_REQUEST = 0x01;
|
||||
public static final byte NOTIFICATION_CMD_CAPABILITIES_RESPONSE = 0x02;
|
||||
public static final byte NOTIFICATION_CMD_SEND = 0x03;
|
||||
public static final byte NOTIFICATION_CMD_REPLY = 0x04;
|
||||
public static final byte NOTIFICATION_CMD_DISMISS = 0x05;
|
||||
public static final byte NOTIFICATION_CMD_REPLY_ACK = 0x06;
|
||||
public static final byte NOTIFICATION_CMD_ICON_REQUEST = 0x10;
|
||||
public static final byte NOTIFICATION_CMD_ICON_REQUEST_ACK = 0x11;
|
||||
public static final byte NOTIFICATION_CMD_PICTURE_REQUEST = 0x19;
|
||||
public static final byte NOTIFICATION_CMD_PICTURE_REQUEST_ACK = 0x1a;
|
||||
public static final byte NOTIFICATION_TYPE_NORMAL = (byte) 0xfa;
|
||||
public static final byte NOTIFICATION_TYPE_CALL = 0x03;
|
||||
public static final byte NOTIFICATION_TYPE_SMS = (byte) 0x05;
|
||||
@ -68,10 +77,17 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
public static final byte NOTIFICATION_CALL_STATE_START = 0x00;
|
||||
public static final byte NOTIFICATION_CALL_STATE_END = 0x02;
|
||||
|
||||
private int version = -1;
|
||||
private boolean supportsPictures = false;
|
||||
private boolean supportsNotificationKey = false;
|
||||
|
||||
// Keep track of Notification ID -> action handle, as BangleJSDeviceSupport.
|
||||
// This needs to be simplified.
|
||||
private final LimitedQueue<Integer, Long> mNotificationReplyAction = new LimitedQueue<>(16);
|
||||
|
||||
// Keep track of notification pictures
|
||||
private final LimitedQueue<Integer, String> mNotificationPictures = new LimitedQueue<>(16);
|
||||
|
||||
private final ZeppOsFileTransferService fileTransferService;
|
||||
|
||||
public ZeppOsNotificationService(final ZeppOsSupport support, final ZeppOsFileTransferService fileTransferService) {
|
||||
@ -84,13 +100,45 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
return ENDPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(final TransactionBuilder builder) {
|
||||
requestCapabilities(builder);
|
||||
}
|
||||
|
||||
public void requestCapabilities(final TransactionBuilder builder) {
|
||||
write(builder, NOTIFICATION_CMD_CAPABILITIES_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(final byte[] payload) {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
|
||||
final byte cmd = buf.get();
|
||||
|
||||
final GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl();
|
||||
final GBDeviceEventCallControl deviceEvtCallControl = new GBDeviceEventCallControl();
|
||||
|
||||
switch (payload[0]) {
|
||||
case NOTIFICATION_CMD_REPLY:
|
||||
switch (cmd) {
|
||||
case NOTIFICATION_CMD_CAPABILITIES_RESPONSE: {
|
||||
version = buf.get() & 0xff;
|
||||
if (version < 4 || version > 5) {
|
||||
// Untested, might work, might not..
|
||||
LOG.warn("Unsupported notification service version {}", version);
|
||||
}
|
||||
if (version >= 4) {
|
||||
final short unk1 = buf.getShort(); // 100
|
||||
final byte unk2 = buf.get(); // 1
|
||||
final byte unk3 = buf.get(); // 1
|
||||
final short unk4count = buf.getShort();
|
||||
buf.get(new byte[unk4count]);
|
||||
}
|
||||
if (version >= 5) {
|
||||
supportsPictures = buf.get() != 0;
|
||||
supportsNotificationKey = buf.get() != 0;
|
||||
}
|
||||
LOG.info("Notification service version={}, supportsPictures={}", version, supportsPictures);
|
||||
break;
|
||||
}
|
||||
case NOTIFICATION_CMD_REPLY: {
|
||||
// TODO make this configurable?
|
||||
final int notificationId = BLETypeConversions.toUint32(subarray(payload, 1, 5));
|
||||
final Long replyHandle = mNotificationReplyAction.lookup(notificationId);
|
||||
@ -114,6 +162,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
ackNotificationReply(notificationId); // FIXME: premature?
|
||||
deleteNotification(notificationId); // FIXME: premature?
|
||||
return;
|
||||
}
|
||||
case NOTIFICATION_CMD_DISMISS:
|
||||
switch (payload[1]) {
|
||||
case NOTIFICATION_DISMISS_NOTIFICATION:
|
||||
@ -138,7 +187,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
LOG.warn("Unexpected notification dismiss byte {}", String.format("0x%02x", payload[1]));
|
||||
return;
|
||||
}
|
||||
case NOTIFICATION_CMD_ICON_REQUEST:
|
||||
case NOTIFICATION_CMD_ICON_REQUEST: {
|
||||
final String packageName = StringUtils.untilNullTerminator(payload, 1);
|
||||
if (packageName == null) {
|
||||
LOG.error("Failed to decode package name from payload");
|
||||
@ -159,6 +208,31 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
int height = BLETypeConversions.toUint16(subarray(payload, pos, pos + 2));
|
||||
sendIconForPackage(packageName, iconFormat, width, height);
|
||||
return;
|
||||
}
|
||||
case NOTIFICATION_CMD_PICTURE_REQUEST: {
|
||||
final String packageName = StringUtils.untilNullTerminator(buf);
|
||||
if (packageName == null) {
|
||||
LOG.error("Failed to decode package name for picture from payload");
|
||||
return;
|
||||
}
|
||||
|
||||
final int notificationId = buf.getInt();
|
||||
final byte pictureFormat = buf.get();
|
||||
final int width = buf.getShort();
|
||||
final int height = buf.getShort();
|
||||
|
||||
LOG.info(
|
||||
"Got notification picture request for {}, {}, {}, {}x{}",
|
||||
packageName,
|
||||
notificationId,
|
||||
pictureFormat,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
sendNotificationPicture(packageName, notificationId, pictureFormat, width);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
LOG.warn("Unexpected notification byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
@ -264,7 +338,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
|
||||
// reply
|
||||
boolean hasReply = false;
|
||||
if (notificationSpec.attachedActions != null && notificationSpec.attachedActions.size() > 0) {
|
||||
if (notificationSpec.attachedActions != null && !notificationSpec.attachedActions.isEmpty()) {
|
||||
for (int i = 0; i < notificationSpec.attachedActions.size(); i++) {
|
||||
final NotificationSpec.Action action = notificationSpec.attachedActions.get(i);
|
||||
|
||||
@ -281,6 +355,19 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
}
|
||||
|
||||
baos.write((byte) (hasReply ? 1 : 0));
|
||||
if (version >= 5) {
|
||||
baos.write(1); // ?
|
||||
}
|
||||
if (supportsPictures) {
|
||||
baos.write((byte) (notificationSpec.picturePath != null ? 1 : 0));
|
||||
if (notificationSpec.picturePath != null) {
|
||||
mNotificationPictures.add(notificationSpec.getId(), notificationSpec.picturePath);
|
||||
}
|
||||
}
|
||||
if (supportsNotificationKey) {
|
||||
baos.write(notificationSpec.key.getBytes(StandardCharsets.UTF_8));
|
||||
baos.write(0);
|
||||
}
|
||||
|
||||
write(builder, baos.toByteArray());
|
||||
builder.queue(getSupport().getQueue());
|
||||
@ -325,7 +412,11 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
write("ack notification reply", buf.array());
|
||||
}
|
||||
|
||||
private void ackNotificationAfterIconSent(final String queuedIconPackage) {
|
||||
private void ackNotificationAfterIconSent(final String queuedIconPackage, final boolean success) {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Acknowledging icon send for {}", queuedIconPackage);
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(1 + queuedIconPackage.length() + 1 + 1);
|
||||
@ -333,33 +424,29 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
buf.put(NOTIFICATION_CMD_ICON_REQUEST_ACK);
|
||||
buf.put(queuedIconPackage.getBytes(StandardCharsets.UTF_8));
|
||||
buf.put((byte) 0x00);
|
||||
buf.put((byte) 0x01);
|
||||
buf.put((byte) 0x01); // TODO !success?
|
||||
|
||||
write("ack icon send", buf.array());
|
||||
}
|
||||
|
||||
private void sendIconForPackage(final String packageName, final byte iconFormat, final int width, final int height) {
|
||||
if (getSupport().getMTU() < 247) {
|
||||
LOG.warn("Sending icons requires high MTU, current MTU is {}", getSupport().getMTU());
|
||||
return;
|
||||
}
|
||||
private void ackNotificationAfterPictureSent(final String packageName, final int notificationId, final boolean success) {
|
||||
LOG.info("Acknowledging picture send for {}", packageName);
|
||||
|
||||
// Without the expected tga id and format string they seem to get corrupted,
|
||||
// but the encoding seems to actually be the same...?
|
||||
final String format;
|
||||
final String tgaId;
|
||||
switch (iconFormat) {
|
||||
case 0x04:
|
||||
format = "TGA_RGB565_GCNANOLITE";
|
||||
tgaId = "SOMHP";
|
||||
break;
|
||||
case 0x08:
|
||||
format = "TGA_RGB565_DAVE2D";
|
||||
tgaId = "SOMH6";
|
||||
break;
|
||||
default:
|
||||
LOG.error("Unknown icon format {}", String.format("0x%02x", iconFormat));
|
||||
return;
|
||||
final ByteBuffer buf = ByteBuffer.allocate(1 + packageName.length() + 1 + 4 + 1).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.put(NOTIFICATION_CMD_PICTURE_REQUEST_ACK);
|
||||
buf.put(packageName.getBytes(StandardCharsets.UTF_8));
|
||||
buf.put((byte) 0x00);
|
||||
buf.putInt(notificationId);
|
||||
buf.put((byte) (success ? 0x01 : 0x02));
|
||||
|
||||
write("ack picture send", buf.array());
|
||||
}
|
||||
|
||||
private void sendIconForPackage(final String packageName, final byte iconFormat, final int width, final int height) {
|
||||
final BitmapFormat format = BitmapFormat.fromCode(iconFormat);
|
||||
if (format == null) {
|
||||
LOG.error("Unknown icon bitmap format code {}", String.format("0x%02x", iconFormat));
|
||||
return;
|
||||
}
|
||||
|
||||
final Drawable icon = NotificationUtils.getAppIcon(getContext(), packageName);
|
||||
@ -369,12 +456,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
}
|
||||
|
||||
final Bitmap bmp = BitmapUtil.toBitmap(icon);
|
||||
|
||||
// The TGA needs to have this ID, or the band does not accept it
|
||||
final byte[] tgaIdBytes = new byte[46];
|
||||
System.arraycopy(tgaId.getBytes(StandardCharsets.UTF_8), 0, tgaIdBytes, 0, 5);
|
||||
|
||||
final byte[] tga565 = BitmapUtil.convertToTgaRGB565(bmp, width, height, tgaIdBytes);
|
||||
final byte[] tga = encodeBitmap(bmp, format, width, height);
|
||||
|
||||
final String url = String.format(
|
||||
Locale.ROOT,
|
||||
@ -386,22 +468,76 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
);
|
||||
final String filename = String.format("logo_%s.tga", packageName.replace(".", "_"));
|
||||
|
||||
sendFile(url, filename, tga, false, success -> ackNotificationAfterIconSent(packageName, success));
|
||||
}
|
||||
|
||||
private void sendNotificationPicture(final String packageName, final int notificationId, final byte pictureFormat, final int width) {
|
||||
final BitmapFormat format = BitmapFormat.fromCode(pictureFormat);
|
||||
if (format == null) {
|
||||
LOG.error("Unknown picture bitmap format code {}", String.format("0x%02x", pictureFormat));
|
||||
ackNotificationAfterPictureSent(packageName, notificationId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
final String picturePath = mNotificationPictures.lookup(notificationId);
|
||||
if (picturePath == null) {
|
||||
LOG.warn("Failed to find picture path for {}", notificationId);
|
||||
ackNotificationAfterPictureSent(packageName, notificationId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
final Bitmap bmp = BitmapFactory.decodeFile(picturePath);
|
||||
if (bmp == null) {
|
||||
LOG.warn("Failed to decode bitmap from {}", picturePath);
|
||||
ackNotificationAfterPictureSent(packageName, notificationId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: On the GTR 4, the band sends 358 on the url, but the actual image has 368 width
|
||||
// if sent as requested, it gets all corrupted...
|
||||
final int targetWidth = width + 10;
|
||||
final int targetHeight = (int) Math.round(bmp.getHeight() * ((double) targetWidth / bmp.getWidth()));
|
||||
final byte[] tga = encodeBitmap(bmp, format, targetWidth, targetHeight);
|
||||
|
||||
final String url = String.format(
|
||||
Locale.ROOT,
|
||||
"notification://content_image?app_id=%s&uid=%d&width=%d&height=%d&format=%s",
|
||||
packageName,
|
||||
notificationId,
|
||||
width,
|
||||
targetHeight,
|
||||
format
|
||||
);
|
||||
final String filename = String.format(Locale.ROOT, "picture_%d.tga", notificationId);
|
||||
|
||||
sendFile(url, filename, tga, true, success -> ackNotificationAfterPictureSent(packageName, notificationId, success));
|
||||
}
|
||||
|
||||
private void sendFile(final String url,
|
||||
final String filename,
|
||||
final byte[] bytes,
|
||||
final boolean compress,
|
||||
final Consumer<Boolean> uploadFinishCallback) {
|
||||
if (getSupport().getMTU() < 247) {
|
||||
LOG.warn("Sending files requires high MTU, current MTU is {}", getSupport().getMTU());
|
||||
return;
|
||||
}
|
||||
|
||||
fileTransferService.sendFile(
|
||||
url,
|
||||
filename,
|
||||
tga565,
|
||||
bytes,
|
||||
true,
|
||||
new ZeppOsFileTransferService.Callback() {
|
||||
@Override
|
||||
public void onFileUploadFinish(final boolean success) {
|
||||
LOG.info("Finished sending icon, success={}", success);
|
||||
if (success) {
|
||||
ackNotificationAfterIconSent(packageName);
|
||||
}
|
||||
LOG.info("Finished sending '{}' to '{}', success={}", filename, url, success);
|
||||
uploadFinishCallback.accept(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileUploadProgress(final int progress) {
|
||||
LOG.trace("Icon send progress: {}", progress);
|
||||
LOG.trace("File send progress: {}", progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -411,6 +547,50 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
}
|
||||
);
|
||||
|
||||
LOG.info("Queueing icon for {}", packageName);
|
||||
LOG.info("Queueing file send '{}' to '{}'", filename, url);
|
||||
}
|
||||
|
||||
private static byte[] encodeBitmap(final Bitmap bmp, final BitmapFormat format, final int width, final int height) {
|
||||
// Without the expected tga id and format string they seem to get corrupted,
|
||||
// but the encoding seems to actually be the same...?
|
||||
// The TGA needs to have this ID, or the band does not accept it
|
||||
final byte[] tgaIdBytes = new byte[46];
|
||||
//System.arraycopy(format.getTgaId().getBytes(StandardCharsets.UTF_8), 0, tgaIdBytes, 0, 5);
|
||||
System.arraycopy(GB.hexStringToByteArray("534F4D486601"), 0, tgaIdBytes, 0, 6);
|
||||
|
||||
return BitmapUtil.convertToTgaRGB565(bmp, width, height, tgaIdBytes);
|
||||
}
|
||||
|
||||
private enum BitmapFormat {
|
||||
TGA_RGB565_GCNANOLITE(0x04, "SOMHP"),
|
||||
TGA_RGB565_DAVE2D(0x08, "SOMH6"),
|
||||
;
|
||||
|
||||
private final byte code;
|
||||
private final String tgaId;
|
||||
|
||||
BitmapFormat(final int code, final String tgaId) {
|
||||
this.code = (byte) code;
|
||||
this.tgaId = tgaId;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getTgaId() {
|
||||
return tgaId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BitmapFormat fromCode(final byte code) {
|
||||
for (final BitmapFormat format : BitmapFormat.values()) {
|
||||
if (format.code == code) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user