mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Zepp OS: fix notification pictures
This commit is contained in:
+49
-10
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2023-2024 José Rebelo
|
||||
/* Copyright (C) 2023-2026 José Rebelo, Thomas Riedler
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -238,7 +240,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
height
|
||||
);
|
||||
|
||||
sendNotificationPicture(packageName, notificationId, pictureFormat, width);
|
||||
sendNotificationPicture(packageName, notificationId, pictureFormat, width, height);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
@@ -500,7 +502,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
sendFile(url, filename, tga, success -> ackNotificationAfterIconSent(packageName, success));
|
||||
}
|
||||
|
||||
private void sendNotificationPicture(final String packageName, final int notificationId, final byte pictureFormat, final int width) {
|
||||
private void sendNotificationPicture(final String packageName, final int notificationId, final byte pictureFormat, final int requestWidth, final int requestHeight) {
|
||||
final ZeppOsBitmapFormat format = ZeppOsBitmapFormat.fromCode(pictureFormat);
|
||||
if (format == null) {
|
||||
LOG.error("Unknown picture bitmap format code {}", String.format("0x%02x", pictureFormat));
|
||||
@@ -522,11 +524,9 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
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 = format.encode(bmp, targetWidth, targetHeight);
|
||||
TargetDimensions targetDimensions = getTargetDimensions(requestWidth, requestHeight, bmp.getWidth(), bmp.getHeight());
|
||||
|
||||
final byte[] tga = format.encode(bmp, targetDimensions.targetWidth(), targetDimensions.targetHeight());
|
||||
if (tga == null) {
|
||||
LOG.warn("Failed to encode tga from {}", picturePath);
|
||||
ackNotificationAfterPictureSent(packageName, notificationId, false);
|
||||
@@ -538,8 +538,8 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
"notification://content_image?app_id=%s&uid=%d&width=%d&height=%d&format=%s",
|
||||
packageName,
|
||||
notificationId,
|
||||
width,
|
||||
targetHeight,
|
||||
targetDimensions.notificationWidth(),
|
||||
targetDimensions.targetHeight(),
|
||||
format
|
||||
);
|
||||
final String filename = String.format(Locale.ROOT, "picture_%d.tga", notificationId);
|
||||
@@ -547,6 +547,45 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
|
||||
sendFile(url, filename, tga, success -> ackNotificationAfterPictureSent(packageName, notificationId, success));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static TargetDimensions getTargetDimensions(int requestWidth, int requestHeight, int bmpWidth, int bmpHeight) {
|
||||
final boolean is_portrait = bmpHeight > bmpWidth;
|
||||
|
||||
// max height for portrait mode as observed on a Balance 2 XT
|
||||
final int maxHeight = 632;
|
||||
final int requestMaxHeight = Math.min(requestHeight, maxHeight);
|
||||
|
||||
int targetHeight;
|
||||
int targetWidth;
|
||||
int notificationWidth;
|
||||
if (is_portrait && (requestMaxHeight > 0)) {
|
||||
targetHeight = requestMaxHeight;
|
||||
// rounding factor found by testing on snooped data
|
||||
notificationWidth = (requestMaxHeight * 1408) / bmpHeight * bmpWidth / 1408;
|
||||
}
|
||||
else {
|
||||
notificationWidth = requestWidth;
|
||||
// rounding factor found by testing on snooped data
|
||||
targetHeight = bmpHeight * 500 / bmpWidth * requestWidth / 500;
|
||||
}
|
||||
// According to testing, the target width is the smallest multiple of 16 >= notificationWidth
|
||||
// (lower 4 bits are zero)
|
||||
targetWidth = nextMultipleOfSixteen(notificationWidth);
|
||||
return new TargetDimensions(targetHeight, targetWidth, notificationWidth);
|
||||
}
|
||||
|
||||
private record TargetDimensions(int targetHeight, int targetWidth, int notificationWidth) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next greater multiple of 16, or the number itself if it is already a multiple of 16
|
||||
* @param number some positive number
|
||||
* @return smallest multiple of 16 >= number
|
||||
*/
|
||||
private static int nextMultipleOfSixteen(final int number) {
|
||||
return ((number - 1) | 0x0F ) + 1;
|
||||
}
|
||||
|
||||
private void sendFile(final String url,
|
||||
final String filename,
|
||||
final byte[] bytes,
|
||||
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/* Copyright (C) 2023-2026 Thomas Riedler
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
|
||||
public class ZeppOsNotificationServiceTest extends TestBase {
|
||||
private Method getTargetDimensionsMethod() throws NoSuchMethodException {
|
||||
Method method = ZeppOsNotificationService.class.getDeclaredMethod("getTargetDimensions", int.class, int.class, int.class, int.class);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
|
||||
private int[] getTargetDimensions(int requestWidth, int requestHeight, int bmpWidth, int bmpHeight) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Object targetDimensions = getTargetDimensionsMethod().invoke(null, requestWidth, requestHeight, bmpWidth, bmpHeight);
|
||||
Class<?> recordClass = targetDimensions.getClass();
|
||||
|
||||
int actualTargetHeight = (int) recordClass.getDeclaredMethod("targetHeight").invoke(targetDimensions);
|
||||
int actualTargetWidth = (int) recordClass.getDeclaredMethod("targetWidth").invoke(targetDimensions);
|
||||
int actualNotificationWidth = (int) recordClass.getDeclaredMethod("notificationWidth").invoke(targetDimensions);
|
||||
|
||||
return new int[]{actualTargetHeight, actualTargetWidth, actualNotificationWidth};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait2x3_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 705, 1057);
|
||||
assertEquals("Target Width mismatch", 160, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 159, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait2x3_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 705, 1057);
|
||||
assertEquals("Target Width mismatch", 432, dims[1]);
|
||||
assertEquals("Target Height mismatch", 632, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 421, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 632, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait3x4_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 642, 855);
|
||||
assertEquals("Target Width mismatch", 192, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 180, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait3x4_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 642, 855);
|
||||
assertEquals("Target Width mismatch", 480, dims[1]);
|
||||
assertEquals("Target Height mismatch", 632, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 474, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 632, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait3x5_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 686, 1143);
|
||||
assertEquals("Target Width mismatch", 144, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 143, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait3x5_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 686, 1143);
|
||||
assertEquals("Target Width mismatch", 384, dims[1]);
|
||||
assertEquals("Target Height mismatch", 632, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 379, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 632, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait4x5_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 756, 945);
|
||||
assertEquals("Target Width mismatch", 192, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 191, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait4x5_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 756, 945);
|
||||
assertEquals("Target Width mismatch", 512, dims[1]);
|
||||
assertEquals("Target Height mismatch", 632, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 505, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 632, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait9x16_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 756, 1344);
|
||||
assertEquals("Target Width mismatch", 144, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 134, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Portrait9x16_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 756, 1344);
|
||||
assertEquals("Target Width mismatch", 368, dims[1]);
|
||||
assertEquals("Target Height mismatch", 632, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 355, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 632, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape3x2_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 1008, 672);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 159, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 159, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape3x2_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 1008, 672);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 263, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 263, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape4x3_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 1008, 756);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 180, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 180, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape4x3_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 1008, 756);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 297, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 297, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape5x3_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 1008, 604);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 143, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 143, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape5x3_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 1008, 604);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 236, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 236, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape5x4_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 945, 756);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 192, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 192, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape5x4_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 945, 756);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 316, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 316, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape16x9_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 1008, 567);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 134, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 134, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Landscape16x9_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 1008, 567);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 222, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 222, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Quadratic_small() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(240, 240, 743, 743);
|
||||
assertEquals("Target Width mismatch", 240, dims[1]);
|
||||
assertEquals("Target Height mismatch", 240, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 240, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 240, dims[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBalance2XTNotificationDimensions_Quadratic_full() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
int[] dims = getTargetDimensions(396, 932, 743, 743);
|
||||
assertEquals("Target Width mismatch", 400, dims[1]);
|
||||
assertEquals("Target Height mismatch", 396, dims[0]);
|
||||
assertEquals("Notification Width mismatch", 396, dims[2]);
|
||||
assertEquals("Notification Height mismatch", 396, dims[0]);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user