1
0
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:
José Rebelo 2024-11-23 22:04:42 +00:00
parent 4aa145560a
commit 9174d95894
5 changed files with 286 additions and 58 deletions

View File

@ -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;

View File

@ -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

View File

@ -54,6 +54,7 @@ public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<ZeppOsS
"sport://file_transfer?appId=7073283073&params={}",
"track_" + file.getTimestamp() + ".dat",
fileBytes,
false,
this
);
}

View File

@ -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;
}

View File

@ -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;
}
}
}