1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-09 11:47:04 +01:00

Xiaomi: add support for different bitmap pixel formats, fix notification icons

This commit is contained in:
MrYoranimo 2024-09-17 00:18:27 +02:00
parent 47152bfaa2
commit bc814b31e7
3 changed files with 192 additions and 19 deletions

View File

@ -0,0 +1,170 @@
/* Copyright (C) 2024 Yoran Vulker
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class XiaomiBitmapUtils {
private static final Logger LOG = LoggerFactory.getLogger(XiaomiBitmapUtils.class);
public static final int PIXEL_FORMAT_RGB_565_LE = 0;
public static final int PIXEL_FORMAT_RGB_565_BE = 1;
public static final int PIXEL_FORMAT_XRGB_8888_LE = 2;
public static final int PIXEL_FORMAT_ARGB_8888_LE = 3;
public static final int PIXEL_FORMAT_ARGB_8565_LE = 7;
public static final int PIXEL_FORMAT_ABGR_8565_LE = 8;
public static String getPixelFormatString(final int pixelFormat) {
switch (pixelFormat) {
case PIXEL_FORMAT_RGB_565_LE:
return "RGB_565_LE";
case PIXEL_FORMAT_RGB_565_BE:
return "RGB_565_BE";
case PIXEL_FORMAT_XRGB_8888_LE:
return "XRGB_8888_LE";
case PIXEL_FORMAT_ARGB_8888_LE:
return "ARGB_8888_LE";
case PIXEL_FORMAT_ARGB_8565_LE:
return "ARGB_8565_LE";
case PIXEL_FORMAT_ABGR_8565_LE:
return "ABGR_8565_LE";
}
return "UNKNOWN";
}
private static Bitmap fit(final Drawable drawable, final int width, final int height) {
final Rect originalBounds = drawable.copyBounds();
final Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(result);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
drawable.setBounds(originalBounds);
return result;
}
public static byte[] convertToRgb565(final Bitmap bitmap, final boolean littleEndian) {
final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 2);
if (littleEndian)
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int y = 0; y < bitmap.getHeight(); y++) {
for (int x = 0; x < bitmap.getWidth(); x++) {
final int pixel = bitmap.getPixel(x, y);
final int r = (pixel >> 19) & 0x1f,
g = (pixel >> 10) & 0x3f,
b = pixel & 0x1f;
buffer.putShort((short) ((r << 11) | (g << 5) | b));
}
}
return buffer.array();
}
public static byte[] convertToRgb565L(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
final byte[] rawBitmap = convertToRgb565(bitmap, true);
bitmap.recycle();
return rawBitmap;
}
public static byte[] convertToRgb565B(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
final byte[] rawBitmap = convertToRgb565(bitmap, true);
bitmap.recycle();
return rawBitmap;
}
public static byte[] convertToArgb8565(final Bitmap bitmap, final boolean swapChannels) {
final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 3).order(ByteOrder.LITTLE_ENDIAN);
for (int y = 0; y < bitmap.getHeight(); y++) {
for (int x = 0; x < bitmap.getWidth(); x++) {
final int pixel = bitmap.getPixel(x, y);
final int a = (pixel >> 24) & 0xff,
r = (pixel >> 19) & 0x1f,
g = (pixel >> 10) & 0x3f,
b = (pixel >> 3) & 0x1f;
// emulate int24
buffer.putShort((short) (((swapChannels ? b : r) << 11) | (g << 5) | (swapChannels ? r : b)));
buffer.put((byte) a);
}
}
return buffer.array();
}
public static byte[] convertToArgb8565(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
final byte[] rawBitmap = convertToArgb8565(bitmap, false);
bitmap.recycle();
return rawBitmap;
}
public static byte[] convertToAbgr8565(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
final byte[] rawBitmap = convertToArgb8565(bitmap, true);
bitmap.recycle();
return rawBitmap;
}
public static byte[] convertToArgb8888(final Bitmap bitmap) {
final ByteBuffer buffer = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 4).order(ByteOrder.LITTLE_ENDIAN);
for (int y = 0; y < bitmap.getHeight(); y++) {
for (int x = 0; x < bitmap.getWidth(); x++) {
buffer.putInt(bitmap.getPixel(x, y));
}
}
return buffer.array();
}
public static byte[] convertToArgb8888(final Drawable drawable, final int boundsWidth, final int boundsHeight) {
final Bitmap bitmap = fit(drawable, boundsWidth, boundsHeight);
final byte[] rawBitmap = convertToArgb8888(bitmap);
bitmap.recycle();
return rawBitmap;
}
public static byte[] convertToPixelFormat(final int pixelFormat, final Drawable drawable, final int boundsWidth, final int boundsHeight) {
switch (pixelFormat) {
case PIXEL_FORMAT_RGB_565_LE:
return convertToRgb565L(drawable, boundsWidth, boundsHeight);
case PIXEL_FORMAT_RGB_565_BE:
return convertToRgb565B(drawable, boundsWidth, boundsHeight);
case PIXEL_FORMAT_XRGB_8888_LE:
case PIXEL_FORMAT_ARGB_8888_LE:
return convertToArgb8888(drawable, boundsWidth, boundsHeight);
case PIXEL_FORMAT_ARGB_8565_LE:
return convertToArgb8565(drawable, boundsWidth, boundsHeight);
case PIXEL_FORMAT_ABGR_8565_LE:
return convertToAbgr8565(drawable, boundsWidth, boundsHeight);
}
LOG.error("Unknown pixel format {}", pixelFormat);
return null;
}
}

View File

@ -52,6 +52,9 @@ import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBitmapUtils.convertToPixelFormat;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiBitmapUtils.getPixelFormatString;
public class XiaomiNotificationService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(XiaomiNotificationService.class);
@ -519,11 +522,20 @@ public class XiaomiNotificationService extends AbstractXiaomiService implements
return;
}
int unk1 = notificationIconRequest.getUnknown1();
int unk2 = notificationIconRequest.getUnknown2();
final int status = notificationIconRequest.getStatus();
final int pixelFormat = notificationIconRequest.getPixelFormat();
final int size = notificationIconRequest.getSize();
LOG.debug("Got notification icon request for size {} for {}, unk1={}, unk2={}", size, iconPackageName, unk1, unk2);
LOG.debug("Got notification icon request for size {} for {}, status={}, pixelFormat={} ({})",
size,
iconPackageName,
status,
getPixelFormatString(pixelFormat),
pixelFormat);
if (status != 0) {
return;
}
final Drawable icon = NotificationUtils.getAppIcon(getSupport().getContext(), iconPackageName);
if (icon == null) {
@ -532,23 +544,14 @@ public class XiaomiNotificationService extends AbstractXiaomiService implements
return;
}
final Bitmap bmp = BitmapUtil.toBitmap(icon);
final Bitmap bmpResized = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bmpResized);
final Rect rect = new Rect(0, 0, size, size);
canvas.drawBitmap(bmp, null, rect, null);
// convert from RGBA To ABGR
final ByteBuffer buf = ByteBuffer.allocate(size * size * 4).order(ByteOrder.LITTLE_ENDIAN);
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
//noinspection SuspiciousNameCombination x and y are flipped on purpose
buf.putInt(bmpResized.getPixel(y, x));
}
final byte[] bitmap = convertToPixelFormat(pixelFormat, icon, size, size);
if (bitmap == null || bitmap.length == 0) {
LOG.error("Failed to convert app icon");
return;
}
getSupport().getDataUploadService().setCallback(this);
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_NOTIFICATION_ICON, buf.array());
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_NOTIFICATION_ICON, bitmap);
}
@Override

View File

@ -718,8 +718,8 @@ message CannedMessages {
}
message NotificationIconRequest {
optional uint32 unknown1 = 1; // 0 probably format
optional uint32 unknown2 = 2; // 3 probably format
optional uint32 status = 1; // 0
optional uint32 pixelFormat = 2; // see XiaomiBitmapUtils
optional uint32 size = 3;
}