From 62d8b69e3e3a6c847ccdb00dd8973c5b5f43ed8a Mon Sep 17 00:00:00 2001 From: Reza Roudoci Date: Thu, 3 Sep 2026 15:35:49 +0200 Subject: [PATCH] Modif couleurs, with/without response sdk --- .../com/activelook/activelooksdk/Glasses.java | 25 +- .../activelooksdk/SdkSingleton.java | 4 +- .../activelooksdk/core/AbstractGlasses.java | 85 +++++- .../activelooksdk/core/Command.java | 28 +- .../activelooksdk/core/CommandData.java | 5 + .../activelooksdk/core/Payload.java | 10 +- .../activelooksdk/core/PayloadDecoder2.java | 10 +- .../core/ble/BleSdkSingleton.java | 4 +- .../core/ble/DiscoveredGlassesImpl.java | 4 +- .../core/ble/GlassesGattCallbackImpl.java | 274 +++++++++++------- .../activelooksdk/core/ble/GlassesImpl.java | 20 +- .../core/ble/GlassesUpdater.java | 1 - .../core/ble/UpdateGlassesTask.java | 83 +++--- .../core/debug/DebugSdkSingleton.java | 2 +- .../activelooksdk/core/debug/GlassesImpl.java | 2 +- .../activelooksdk/types/FontInfo.java | 2 +- .../activelooksdk/types/ImageConverter.java | 11 +- .../activelooksdk/types/ImageMDP05.java | 13 +- .../activelooksdk/types/ImgSaveFormat.java | 8 + .../activelooksdk/types/PageInfo.java | 3 +- .../activelooksdk/types/PayloadDecoder.java | 36 +-- .../activelook/activelooksdk/types/Utils.java | 6 +- 22 files changed, 422 insertions(+), 214 deletions(-) diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/Glasses.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/Glasses.java index 79c5e1f..b0da42b 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/Glasses.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/Glasses.java @@ -32,7 +32,6 @@ import com.activelook.activelooksdk.types.GaugeInfo; import com.activelook.activelooksdk.types.GlassesSettings; import com.activelook.activelooksdk.types.GlassesVersion; -import com.activelook.activelooksdk.types.Image1bppData; import com.activelook.activelooksdk.types.ImageData; import com.activelook.activelooksdk.types.ImageInfo; import com.activelook.activelooksdk.types.ImgStreamFormat; @@ -147,7 +146,7 @@ default void unsubscribeToSensorInterfaceNotifications() { * * @param on Turn on or off */ - void isWriteWithResponse(boolean on); + void setWriteWithResponse(boolean on); /** * Load a configuration into the glasses. @@ -178,6 +177,8 @@ default void unsubscribeToSensorInterfaceNotifications() { * Send demo command. * {@link Deprecated} */ + + void grayscale(byte level); void demo(); /** * Send the firmware 4.0.0 demo command with parameter which was the test. @@ -248,11 +249,20 @@ default void unsubscribeToSensorInterfaceNotifications() { */ void als(boolean enable); /** - * Sets the grey level (0 to 15) used to draw the next graphical element. - * + * Sets the red-green color value (0 to 255) used to draw the next graphical element. + * Only available on color glasses (see {@link #isColorCapable()}). + * Sets the grey level (0 to 15) used to draw the next graphical element if not color glasses. * @param value The selected color. */ void color(byte value); + /** + * Indicates whether the connected glasses have a color display (MDP08) rather than a + * grey-level one (MDP05), based on the Hardware Version String. + */ + default boolean isColorCapable() { + final String hw = this.getDeviceInformation().getHardwareVersion(); + return hw != null && hw.contains("MDP08"); + } /** * Set a pixel on at the corresponding coordinates. * @@ -381,6 +391,13 @@ default void unsubscribeToSensorInterfaceNotifications() { * @param img The image to store in the configuration . */ void imgSave4bppHeatShrinkSaveComp(byte id, Bitmap img); + /** + * Save image data in the chosen format. + * @param id The image id in the configuration. + * @param imgData The pre-encoded image data to store in the configuration. + * @param format The image format. + */ + void imgSave(byte id, ImageData imgData, ImgSaveFormat format); /** * Display image id to the corresponding coordinates. * diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/SdkSingleton.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/SdkSingleton.java index d9b8bd7..cfc5763 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/SdkSingleton.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/SdkSingleton.java @@ -29,7 +29,7 @@ final class SdkSingleton { private static Sdk singleton; - static final synchronized Sdk init(Context applicationContext, + static synchronized Sdk init(Context applicationContext, String token, Consumer onUpdateStart, Consumer> onUpdateAvailableCallback, @@ -51,7 +51,7 @@ static final synchronized Sdk init(Context applicationContext, return SdkSingleton.singleton; } - static final synchronized Sdk getInstance() { + static synchronized Sdk getInstance() { return SdkSingleton.singleton; } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/AbstractGlasses.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/AbstractGlasses.java index 46a5283..e37d119 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/AbstractGlasses.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/AbstractGlasses.java @@ -58,6 +58,7 @@ public abstract class AbstractGlasses implements Glasses { static final byte ID_power = (byte) 0x00; static final byte ID_clear = (byte) 0x01; static final byte ID_grey = (byte) 0x02; + static final byte ID_grayscale = (byte) 0x30; static final byte ID_demo = (byte) 0x03; static final byte ID_test = (byte) 0x04; static final byte ID_battery = (byte) 0x05; @@ -65,6 +66,8 @@ public abstract class AbstractGlasses implements Glasses { static final byte ID_led = (byte) 0x08; static final byte ID_shift = (byte) 0x09; static final byte ID_settings = (byte) 0x0A; + static final byte ID_error = (byte) 0xE2; + /* * Display luminance commands ids */ @@ -78,7 +81,7 @@ public abstract class AbstractGlasses implements Glasses { /* * Graphics commands ids */ - static final byte ID_color = (byte) 0x30; + static final byte ID_color = (byte) 0x3D; static final byte ID_point = (byte) 0x31; static final byte ID_line = (byte) 0x32; static final byte ID_rect = (byte) 0x33; @@ -86,6 +89,7 @@ public abstract class AbstractGlasses implements Glasses { static final byte ID_circ = (byte) 0x35; static final byte ID_circf = (byte) 0x36; static final byte ID_txt = (byte) 0x37; + static final byte ID_txtColor = (byte) 0x3E; static final byte ID_polyline = (byte) 0x38; static final byte ID_holdFlush = (byte) 0x39; /* @@ -175,6 +179,10 @@ public abstract class AbstractGlasses implements Glasses { static final byte ID_shutdown = (byte) 0xE0; private final HashMap> callbacks; private QueryId currentQID; + private boolean useQueryId = false; + public void setUseQueryId(boolean useQueryId) { + this.useQueryId = useQueryId; + } /* Methods for children implementation @@ -187,11 +195,18 @@ protected AbstractGlasses() { protected void writeBytes(byte[] bytes) { } + protected void writeBytes(byte[] bytes,boolean writeType) { + } + protected final void delegateToCallback(final Command command) { final QueryId qid = command.getQueryId(); if (qid != null) { final Consumer callback = this.callbacks.remove(qid); if (callback != null) { + if (command.getCommandId() == ID_error) { + Log.e("AbstractGlasses", "Command failed: " + Command.bytesToStr(command.getData())); + return; // c'est un message d'erreur, pas la réponse attendue : on ne le donne pas au parseur + } callback.accept(command.getData()); } } @@ -211,18 +226,39 @@ private QueryId nextQueryId() { } private void writeCommand(final Command command) { - final QueryId qid = this.nextQueryId(); - command.setQueryId(qid); + if (this.useQueryId) { + final QueryId qid = this.nextQueryId(); + command.setQueryId(qid); + } this.writeBytes(command.toBytes()); } private void writeCommand(final Command command, final Consumer callback) { - QueryId qid = this.nextQueryId(); - command.setQueryId(qid); - this.registerCallback(qid, callback); + if (this.useQueryId) { + QueryId qid = this.nextQueryId(); + command.setQueryId(qid); + this.registerCallback(qid, callback); + } else { + Log.w("AbstractGlasses", "Callback registered but useQueryId=false: it will never fire."); + } this.writeBytes(command.toBytes()); } +// private void writeCommand(final Command command, boolean writeType) { +// final QueryId qid = this.nextQueryId(); +// command.setQueryId(qid); +// this.writeBytes(command.toBytes(),writeType); +// } + +// private void writeCommand(final Command command, final Consumer callback, boolean writeType) { +// QueryId qid = this.nextQueryId(); +// command.setQueryId(qid); +// this.registerCallback(qid, callback); +// this.writeBytes(command.toBytes(),writeType); +// } + + + /* Public defaults */ @@ -251,6 +287,14 @@ public void grey(final byte level) { this.writeCommand(new Command(ID_grey, data)); } + @Override + public void grayscale(final byte level) { + final CommandData data = CommandData.fromGreyLevel(level); + this.writeCommand(new Command(ID_grayscale, data)); + } + + + @Override public void demo() { this.writeCommand(new Command(ID_demo)); @@ -330,7 +374,7 @@ public void als(final boolean on) { @Override public void color(final byte value) { - final CommandData data = CommandData.fromGreyLevel(value); + final CommandData data = CommandData.fromColorLevel(value); this.writeCommand(new Command(ID_color, data)); } @@ -377,9 +421,10 @@ public void txt(final short x, final short y, final Rotation r, final byte f, fi .add(CommandData.fromRotation(r)) .addUInt8(f, c) .addNulTerminatedStrings(s); - this.writeCommand(new Command(ID_txt, data)); + this.writeCommand(new Command((isColorCapable() ? ID_txtColor : ID_txt), data)); } + @Override public void polyline(final short[] points) { final CommandData data = new CommandData().addInt16(points); @@ -439,11 +484,16 @@ public void imgSave(final byte id, final Bitmap image, final ImgSaveFormat forma case MONO_4BPP_HEATSHRINK_SAVE_COMP: this.imgSave4bppHeatShrinkSaveComp(id, image); break; + case RG_COLOR_8BPP: + this.imgSaveRGColor8bpp(id, image); + break; + } } + // TODO @Override - public void imgSave4bpp(final byte id, final int width, final int size, final byte[] bytes, final ImgSaveFormat format) { + public void imgSave(final byte id, final int width, final int size, final byte[] bytes, final ImgSaveFormat format) { final CommandData data = new CommandData() .addUInt8(id) .addUInt32(size) @@ -459,21 +509,27 @@ public void imgSave4bpp(final byte id, final int width, final int size, final by public void imgSave4bpp(final byte id, final Bitmap image){ final ImgSaveFormat format = ImgSaveFormat.MONO_4BPP; final ImageData imgData = ImageConverter.getImageData(image, format); - this.imgSave4bpp(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); + this.imgSave(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); } + public void imgSaveRGColor8bpp(final byte id, final Bitmap image){ + final ImgSaveFormat format = ImgSaveFormat.RG_COLOR_8BPP; + final ImageData imgData = ImageConverter.getImageData(image, format); + this.imgSave(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); + } + @Override public void imgSave4bppHeatShrink(final byte id, final Bitmap image){ final ImgSaveFormat format = ImgSaveFormat.MONO_4BPP_HEATSHRINK; final ImageData imgData = ImageConverter.getImageData(image, format); - this.imgSave4bpp(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); + this.imgSave(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); } @Override public void imgSave4bppHeatShrinkSaveComp(final byte id, final Bitmap image){ final ImgSaveFormat format = ImgSaveFormat.MONO_4BPP_HEATSHRINK_SAVE_COMP; final ImageData imgData = ImageConverter.getImageData(image, format); - this.imgSave4bpp(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); + this.imgSave(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); } // TODO @Override @@ -510,7 +566,10 @@ public void imgSave1bpp(final byte id, final Bitmap image){ final Image1bppData imgData = ImageConverter.getImage1bppData(image, format); this.imgSave1bpp(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); } - + @Override + public void imgSave(final byte id, final ImageData imgData, final ImgSaveFormat format) { + this.imgSave(id, imgData.getWidth(), imgData.getSize(), imgData.getBytes(), format); + } @Override public void imgDisplay(final byte id, final short x, final short y) { final CommandData data = new CommandData().addUInt8(id).addInt16(x, y); diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Command.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Command.java index f2e40bd..6e0876d 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Command.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Command.java @@ -45,19 +45,19 @@ public Command(final byte commandId, final CommandData data) { this.commandId = commandId; this.queryId = queryId; } + public byte getCommandId() { + return this.commandId; + } public Command(byte[] payload) { super(); - int fullLength = 5; + int fullLength = getFullLength(payload); assert payload[0] == (byte) 0xFF; this.commandId = payload[1]; int n = payload[2] & (byte) 0x0F; int offset = 4; if ((payload[2] & 0x10) == 0x10) { - fullLength = CommandData.UInt16.asInt(payload[3], payload[4]); offset++; - } else { - fullLength = CommandData.UInt8.asShort(payload[3]); } int m = fullLength - (1 + n + offset); if (n > 0) { @@ -75,19 +75,25 @@ public Command(byte[] payload) { public static final boolean isValidBuffer(byte[] payload) { Log.d("Validating", Command.bytesToStr(payload)); - int fullLength = 5; - assert payload[0] == (byte) 0xFF; + if (payload.length < 5) return false; + int fullLength; if ((payload[2] & 0x10) == 0x10) { + if (payload.length < 5) return false; fullLength = CommandData.UInt16.asInt(payload[3], payload[4]); } else { fullLength = CommandData.UInt8.asShort(payload[3]); } - Log.d("Validating", String.format("payload.length %d == fullLength %d", payload.length, fullLength)); - if (payload.length == fullLength) { - assert payload[fullLength - 1] == (byte) 0xAA; - return true; + return payload.length >= fullLength; + } + + public static final int getFullLength(byte[] payload) { + assert payload.length >= 4 : "Buffer too short to read header"; + assert payload[0] == (byte) 0xFF; + if ((payload[2] & 0x10) == 0x10) { + assert payload.length >= 5 : "Buffer too short to read extended length"; + return CommandData.UInt16.asInt(payload[3], payload[4]); } else { - return false; + return CommandData.UInt8.asShort(payload[3]); } } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/CommandData.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/CommandData.java index bb377dd..fb4e7c1 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/CommandData.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/CommandData.java @@ -257,6 +257,10 @@ public static CommandData fromGreyLevel(final byte level) { return new CommandData(level); } + public static CommandData fromColorLevel(final byte level) { + return new CommandData(level); + } + public static CommandData fromDemoPattern(final DemoPattern pattern) { assert pattern != null : String.format(Locale.US, "Pattern cannot be null"); switch (pattern) { @@ -285,6 +289,7 @@ public static CommandData fromImgSaveFormat(final ImgSaveFormat format) { case MONO_1BPP: return new CommandData((byte) 0x01); case MONO_4BPP_HEATSHRINK: return new CommandData((byte) 0x02); case MONO_4BPP_HEATSHRINK_SAVE_COMP: return new CommandData((byte) 0x03); + case RG_COLOR_8BPP: return new CommandData((byte) 0x0A); default: return new CommandData((byte) 0x04); } } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Payload.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Payload.java index a398f2f..e4e3532 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Payload.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/Payload.java @@ -122,7 +122,7 @@ public static final String bytesToStr(byte[] payload) { * @param data: Can be null. Parameters of the command * @return the byte buffer to write */ - private static final byte[] getBytes(byte commandId, byte[] queryId, byte[] data) { + private static byte[] getBytes(byte commandId, byte[] queryId, byte[] data) { int n = queryId.length; int m = data.length; assert n <= 15 : String.format("QueryId length too big: %d > 15", n); @@ -152,7 +152,7 @@ private static final byte[] getBytes(byte commandId, byte[] queryId, byte[] data return payload; } - private static final byte[] getBytesWithData(byte commandId, byte[] data) { + private static byte[] getBytesWithData(byte commandId, byte[] data) { int m = data.length; assert m <= 512 : String.format("Data length too big: %d > 512", m); int fullLength = 5 + m; @@ -178,7 +178,7 @@ private static final byte[] getBytesWithData(byte commandId, byte[] data) { return payload; } - private static final byte[] getBytesWithQueryId(byte commandId, byte[] queryId) { + private static byte[] getBytesWithQueryId(byte commandId, byte[] queryId) { int n = queryId.length; assert n <= 15 : String.format("QueryId length too big: %d > 15", n); int fullLength = 5 + n; @@ -194,7 +194,7 @@ private static final byte[] getBytesWithQueryId(byte commandId, byte[] queryId) return payload; } - private static final byte[] combine(byte[]... parameters) { + private static byte[] combine(byte[]... parameters) { int fullLength = 0; for (byte[] parameter : parameters) { fullLength += parameter.length; @@ -208,7 +208,7 @@ private static final byte[] combine(byte[]... parameters) { return result; } - private static final byte[] getBytes(byte commandId) { + private static byte[] getBytes(byte commandId) { return new byte[]{(byte) 0xFF, commandId, (byte) 0x00, (byte) 0x05, (byte) 0xAA}; } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/PayloadDecoder2.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/PayloadDecoder2.java index 17062e1..3e1fbb8 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/PayloadDecoder2.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/PayloadDecoder2.java @@ -11,7 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. -*/ +*//* + package com.activelook.activelooksdk.core; import com.activelook.activelooksdk.types.GlassesSettings; @@ -47,9 +48,11 @@ public static final GlassesSettings decodeGlassesSettings(final byte [] bytes) { ); } - /* + */ +/* HELPERS FOR DECODING RECEIVED PAYLOADS - */ + *//* + private final byte [] bytes; private int offset; @@ -139,3 +142,4 @@ private boolean readBoolean(final int offset, final int size) { } } +*/ diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/BleSdkSingleton.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/BleSdkSingleton.java index 6faa0f5..6c3d475 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/BleSdkSingleton.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/BleSdkSingleton.java @@ -26,7 +26,7 @@ public final class BleSdkSingleton { private static SdkImpl singleton; - public static final synchronized SdkImpl init(Context applicationContext, + public static synchronized SdkImpl init(Context applicationContext, String token, Consumer onUpdateStart, Consumer> onUpdateAvailableCallback, @@ -40,7 +40,7 @@ public static final synchronized SdkImpl init(Context applicationContext, return BleSdkSingleton.singleton; } - public static final synchronized SdkImpl getInstance() { + public static synchronized SdkImpl getInstance() { return BleSdkSingleton.singleton; } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/DiscoveredGlassesImpl.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/DiscoveredGlassesImpl.java index 1a8f372..3ef5a4a 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/DiscoveredGlassesImpl.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/DiscoveredGlassesImpl.java @@ -58,7 +58,7 @@ protected DiscoveredGlassesImpl(Parcel in) { } } - private static final Map mapRecord(byte[] scanRecord) { + private static Map mapRecord(byte[] scanRecord) { Map ret = new HashMap<>(); int index = 0; while (index < scanRecord.length) { @@ -72,7 +72,7 @@ private static final Map mapRecord(byte[] scanRecord) { return ret; } - private static final String recordToHex(byte[] data) { + private static String recordToHex(byte[] data) { if (data.length == 0) { return ""; } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesGattCallbackImpl.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesGattCallbackImpl.java index b21cca6..382ad9d 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesGattCallbackImpl.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesGattCallbackImpl.java @@ -32,9 +32,9 @@ import com.activelook.activelooksdk.types.Utils; import java.nio.charset.StandardCharsets; -import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Map; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,8 +45,42 @@ @SuppressLint("MissingPermission") class GlassesGattCallbackImpl extends GlassesGatt { + private static final class PendingWrite { + final byte[] bytes; + final Consumer progressCallback; + final int writeType; + + PendingWrite(byte[] bytes, Consumer progressCallback, int writeType) { + this.bytes = bytes; + this.progressCallback = progressCallback; + this.writeType = writeType; + } + } + + private static final class Batch { + final ConcurrentLinkedDeque entries; + final int size; + final int writeType; + + Batch(ConcurrentLinkedDeque entries, int size, int writeType) { + this.entries = entries; + this.size = size; + this.writeType = writeType; + } + } + + private static final class PreparedPayload { + final byte[] payload; + final List notifiers; + + PreparedPayload(byte[] payload, List notifiers) { + this.payload = payload; + this.notifiers = notifiers; + } + } + private final DeviceInformation deviceInfo; - private final ConcurrentLinkedDeque>> pendingWriteRxCharacteristic; + private final ConcurrentLinkedDeque pendingWriteRxCharacteristic; private final AtomicBoolean flowControlCanSend; private final AtomicBoolean isWritingCommand; private BluetoothGatt gatt; @@ -196,16 +230,17 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris if (this.pendingBuffer != null) { this.addPendingBuffer(buffer); buffer = this.pendingBuffer; - if (Command.isValidBuffer(buffer)) { - this.pendingBuffer = null; - final Command command = new Command(buffer); - this.glasses.callCallback(command); - } - } else if (Command.isValidBuffer(buffer)) { - final Command command = new Command(buffer); + this.pendingBuffer = null; + } + while (Command.isValidBuffer(buffer)) { + int fullLength = Command.getFullLength(buffer); + byte[] frame = Arrays.copyOfRange(buffer, 0, fullLength); + final Command command = new Command(frame); this.glasses.callCallback(command); - } else { - this.addPendingBuffer(buffer); + buffer = Arrays.copyOfRange(buffer, fullLength, buffer.length); + } + if (buffer.length > 0) { + this.pendingBuffer = buffer; } } else if (characteristic.getUuid().equals(BleUUID.BatteryLevelCharacteristic)) { final int bl = characteristic.getValue()[0]; @@ -240,6 +275,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris GlassesGattCallbackImpl.this.unstackWriteRxCharacteristic(); } }, 10000, TimeUnit.MILLISECONDS); + executorService.shutdown(); } else if (this.onFlowControlEvent != null) { if (state == (byte) 0x03) { this.onFlowControlEvent.accept(FlowControlStatus.CMD_ERROR); @@ -316,7 +352,11 @@ void writeRxCharacteristic(byte[] bytes) { } void writeRxCharacteristic(byte[] bytes, Consumer progressCallback) { - this.pendingWriteRxCharacteristic.add(new AbstractMap.SimpleImmutableEntry<>(bytes, progressCallback)); + this.writeRxCharacteristic(bytes, progressCallback, this.writeType); + } + + void writeRxCharacteristic(byte[] bytes, Consumer progressCallback, int writeType) { + this.pendingWriteRxCharacteristic.add(new PendingWrite(bytes, progressCallback, writeType)); this.unstackWriteRxCharacteristic(); } @@ -326,99 +366,137 @@ synchronized void unstackWriteRxCharacteristic() { } } - private boolean unstackWriteRxCharacteristicLoop() { - if (this.flowControlCanSend.get() && this.pendingWriteRxCharacteristic.size() > 0 && this.isWritingCommand.compareAndSet(false, true)) { - final ConcurrentLinkedDeque>> stack = new ConcurrentLinkedDeque<>(); - final int writeMTU = this.mtu - 3; - int stackSize = 0; - while (stackSize < writeMTU && this.pendingWriteRxCharacteristic.size() > 0) { - final Map.Entry> entry = this.pendingWriteRxCharacteristic.poll(); - assert entry != null; - final byte [] buffer = entry.getKey(); - if (buffer.length > 0) { - stack.add(entry); - stackSize += buffer.length; - } + private Batch collectBatch(int writeMTU) { // sert à compiler les commandes en batch + final ConcurrentLinkedDeque entries = new ConcurrentLinkedDeque<>(); + int totalSize = 0; + Integer batchWriteType = null; + while (totalSize < writeMTU) { + final PendingWrite next = this.pendingWriteRxCharacteristic.peekFirst(); // on regarde SANS retirer + if (next == null) { + break; // plus rien en attente } - final byte [] payload; - Runnable lastNotifier = null; - if (stackSize > writeMTU) { - payload = new byte [writeMTU]; - final int sizeOutOfPayload = stackSize - writeMTU; - final byte[] remainingBuffer = new byte [sizeOutOfPayload]; - final Map.Entry> lastEntry = stack.pollLast(); - assert lastEntry != null; - final byte[] incompleteBuffer = lastEntry.getKey(); - final Consumer incompleteCallback = lastEntry.getValue(); - final int sizeInPayload = incompleteBuffer.length - sizeOutOfPayload; - final int incompleteBufferOffset = writeMTU - sizeInPayload; - System.arraycopy(incompleteBuffer, 0, payload, incompleteBufferOffset, sizeInPayload); - System.arraycopy(incompleteBuffer, sizeInPayload, remainingBuffer, 0, sizeOutOfPayload); - this.pendingWriteRxCharacteristic.addFirst(new AbstractMap.SimpleImmutableEntry<>(remainingBuffer, incompleteCallback)); - if (incompleteCallback != null) { - lastNotifier = () -> incompleteCallback.accept(sizeInPayload / (double) incompleteBuffer.length); - } - } else { - payload = new byte [stackSize]; + if (batchWriteType != null && next.writeType != batchWriteType) { + break; // writeType incompatible : on arrête le batch, il reste en file } - int offset = 0; - final ArrayList notifiers = new ArrayList<>(); - while (!stack.isEmpty()) { - final Map.Entry> firstEntry = stack.poll(); - assert firstEntry != null; - final byte [] buffer = firstEntry.getKey(); - final Consumer completeCallback = firstEntry.getValue(); - System.arraycopy(buffer, 0, payload, offset, buffer.length); - offset += buffer.length; - if (completeCallback != null) { - notifiers.add(() -> completeCallback.accept(1d)); - } + this.pendingWriteRxCharacteristic.pollFirst(); // confirmé compatible, on le retire pour de vrai + if (next.bytes.length == 0) { + continue; // entrée vide, ignorée } - if (lastNotifier != null) { - notifiers.add(lastNotifier); + entries.add(next); + totalSize += next.bytes.length; + batchWriteType = next.writeType; + } + final int resolvedWriteType = batchWriteType != null ? batchWriteType : this.writeType; + return new Batch(entries, totalSize, resolvedWriteType); + } + + private Runnable splitLastEntry(ConcurrentLinkedDeque stack, byte[] payload, int writeMTU, int sizeOutOfPayload) { // lorsque la dernière commande à inscrire dans le payload a une taille supérieure à la limite + final PendingWrite lastEntry = stack.pollLast(); + if (lastEntry == null) { + throw new IllegalStateException("splitLastEntry called with an empty stack"); + } + final byte[] command = lastEntry.bytes; + final Consumer progressCallback = lastEntry.progressCallback; + final int fittingSize = command.length - sizeOutOfPayload; // partie de la commande qui rentre dans CE paquet + final int fittingOffset = writeMTU - fittingSize; // où la coller dans payload (juste avant la fin) + final byte[] overflow = new byte[sizeOutOfPayload]; // partie qui déborde, pour le PROCHAIN paquet + System.arraycopy(command, 0, payload, fittingOffset, fittingSize); // colle la partie qui rentre + System.arraycopy(command, fittingSize, overflow, 0, sizeOutOfPayload); // sauvegarde la partie qui déborde + this.pendingWriteRxCharacteristic.addFirst(new PendingWrite(overflow, progressCallback, lastEntry.writeType)); // remise en tête de file + if (progressCallback == null) { + return null; + } + return () -> progressCallback.accept(fittingSize / (double) command.length); // progression partielle (ex: 0.75) + } + + private List fillPayload(ConcurrentLinkedDeque entries, byte[] payload) { + int offset = 0; + final List notifiers = new ArrayList<>(); + while (!entries.isEmpty()) { + final PendingWrite entry = entries.poll(); + if (entry == null) { + throw new IllegalStateException("fillPayload called with an empty stack"); } - /* No need to replace the previous mechanism. - sendPayload(payload, notifiers); - */ - boolean rollback = false; - if (!this.flowControlCanSend.get()) { - rollback = true; - Log.e("unstackWriteRxCharacteristicLoop", String.format("Flow control not ready: %s", Utils.bytesToHexString(payload))); - } else if (!this.getRxCharacteristic().setValue(payload)) { + final byte[] command = entry.bytes; + final Consumer progressCallback = entry.progressCallback; + System.arraycopy(command, 0, payload, offset, command.length); // colle cette commande à la suite + offset += command.length; // avance le curseur pour la suivante + if (progressCallback != null) { + notifiers.add(() -> progressCallback.accept(1d)); // commande entière → 100% + } + } + return notifiers; + } + private PreparedPayload buildPayload(Batch batch, int writeMTU) { // construit le payload à partir d'un batch + final ConcurrentLinkedDeque entries = batch.entries; + final byte[] payload; + Runnable overflowNotifier = null; + if (batch.size > writeMTU) { + payload = new byte[writeMTU]; + final int sizeOutOfPayload = batch.size - writeMTU; + overflowNotifier = this.splitLastEntry(entries, payload, writeMTU, sizeOutOfPayload); + } else { + payload = new byte[batch.size]; + } + final List notifiers = this.fillPayload(entries, payload); + if (overflowNotifier != null) { + notifiers.add(overflowNotifier); + } + return new PreparedPayload(payload, notifiers); + } + + private boolean writeToGatt(byte[] payload, int writeType, List notifiers) { + boolean rollback = false; + if (!this.flowControlCanSend.get()) { + rollback = true; + Log.e("unstackWriteRxCharacteristicLoop", String.format("Flow control not ready: %s", Utils.bytesToHexString(payload))); + } else if (!this.getRxCharacteristic().setValue(payload)) { + rollback = true; + Log.e("unstackWriteRxCharacteristicLoop", String.format("Could not update rx: %s", Utils.bytesToHexString(payload))); + } else { + this.getRxCharacteristic().setWriteType(writeType); + if (!this.gatt.writeCharacteristic(this.getRxCharacteristic())) { rollback = true; - Log.e("unstackWriteRxCharacteristicLoop", String.format("Could not update rx: %s", Utils.bytesToHexString(payload))); + this.writeFailCount += 1; + Log.e("unstackWriteRxCharacteristicLoop", String.format("Could not write rx: %s", Utils.bytesToHexString(payload))); } else { - this.getRxCharacteristic().setWriteType(writeType); - if (!this.gatt.writeCharacteristic(this.getRxCharacteristic())) { - rollback = true; - this.writeFailCount += 1; - Log.e("unstackWriteRxCharacteristicLoop", String.format("Could not write rx: %s", Utils.bytesToHexString(payload))); - } else { - for (final Runnable notifier: notifiers) notifier.run(); - } - } - if (rollback) { - // If it fails to write a command n times, - // then we cancel rollback - // NB: 50 is arbitrary, this value may change - if (this.writeFailCount <= 50) { - this.pendingWriteRxCharacteristic.addFirst( - new AbstractMap.SimpleImmutableEntry<>( - payload, - p -> { - for (final Runnable notifier : notifiers) notifier.run(); - } - ) - ); - this.isWritingCommand.set(false); - return false; - } else { - this.writeFailCount = 0; - this.isWritingCommand.set(false); - } + for (final Runnable notifier: notifiers) notifier.run(); } } - return true; + return rollback; + } + + private boolean unstackWriteRxCharacteristicLoop() { + final boolean canWriteNow = this.flowControlCanSend.get() && this.pendingWriteRxCharacteristic.size() > 0; + if (!canWriteNow || !this.isWritingCommand.compareAndSet(false, true)) { + return true; // rien à écrire maintenant, ou une écriture est déjà en cours + } + final int writeMTU = this.mtu - 3; + final Batch batch = this.collectBatch(writeMTU); + final PreparedPayload prepared = this.buildPayload(batch, writeMTU); + final boolean rollback = this.writeToGatt(prepared.payload, batch.writeType, prepared.notifiers); + + if (!rollback) { + return true; // écriture acceptée, rien à rejouer + } + return this.handleWriteFailure(prepared, batch.writeType); + } + + private boolean handleWriteFailure(PreparedPayload prepared, int writeType) { + final boolean shouldRetry = this.writeFailCount <= 50; // NB: 50 est arbitraire, cette valeur peut changer + if (shouldRetry) { + this.pendingWriteRxCharacteristic.addFirst( + new PendingWrite( + prepared.payload, + p -> { for (final Runnable notifier : prepared.notifiers) notifier.run(); }, + writeType + ) + ); + } else { + this.writeFailCount = 0; // trop d'échecs consécutifs, on abandonne ce payload + } + this.isWritingCommand.set(false); + return !shouldRetry; } /* No need to do this for removing recursivity diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesImpl.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesImpl.java index 6ab13d9..0ed368d 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesImpl.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesImpl.java @@ -33,6 +33,7 @@ class GlassesImpl extends AbstractGlasses implements Glasses { + public static final Creator CREATOR = new Creator() { @Override public GlassesImpl createFromParcel(Parcel source) { @@ -91,7 +92,18 @@ protected GlassesImpl(Parcel in) { @Override public void writeBytes(byte[] bytes) { - this.gattCallbacks.writeRxCharacteristic(bytes); + this.gattCallbacks.writeRxCharacteristic( + bytes, + null); + } + + public void writeBytes(byte[] bytes, boolean withResponse) { + this.gattCallbacks.writeRxCharacteristic( + bytes, + null, + withResponse + ? BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + : BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); } @Override @@ -149,8 +161,8 @@ public boolean isFirmwareAtLeast(String version) { final String gVersion = this.getDeviceInformation().getFirmwareVersion(); Log.w("isFirmwareAtLeast", String.format( - "glasses: [%s], argument: [%s] = %d", - gVersion, version, compareFirmwareVersions(gVersion, version))); + "glasses: [%s], argument: [%s] = %d", + gVersion, version, compareFirmwareVersions(gVersion, version))); return compareFirmwareVersions(gVersion, version) >= 0; } @@ -182,7 +194,7 @@ public void setOnDisconnected(Consumer onDisconnected) { } @Override - public void isWriteWithResponse(boolean on) { + public void setWriteWithResponse(boolean on) { if(on){ this.gattCallbacks.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); }else { diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesUpdater.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesUpdater.java index d24e225..b267280 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesUpdater.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/GlassesUpdater.java @@ -4,7 +4,6 @@ import android.util.Pair; import androidx.core.util.Consumer; -import androidx.core.util.Predicate; import com.activelook.activelooksdk.DiscoveredGlasses; import com.activelook.activelooksdk.Glasses; diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/UpdateGlassesTask.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/UpdateGlassesTask.java index d3a8379..23aa598 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/UpdateGlassesTask.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/ble/UpdateGlassesTask.java @@ -7,12 +7,10 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.content.Context; -import android.os.SystemClock; import android.util.Log; import androidx.core.util.Consumer; import androidx.core.util.Pair; -import androidx.core.util.Predicate; import com.activelook.activelooksdk.DiscoveredGlasses; import com.activelook.activelooksdk.Glasses; @@ -185,7 +183,7 @@ private void onUpdateError(final UpdateProgress progress) { } private void onApiFail(final VolleyError error) { - error.printStackTrace(); + Log.e("onApiFail","Error"+ error.getMessage()); if (error.networkResponse != null && error.networkResponse.statusCode == HttpURLConnection.HTTP_FORBIDDEN) { this.onUpdateError(this.progress.withStatus(GlassesUpdate.State.ERROR_UPDATE_FORBIDDEN)); this.onConnectionFail.accept(this.discoveredGlasses); @@ -196,7 +194,7 @@ private void onApiFail(final VolleyError error) { private void onApiJSONException(final JSONException error) { if (error != null) { - error.printStackTrace(); + Log.e("onApiJSONException","Error"+ error.getMessage()); } this.onUpdateError(this.progress.withStatus(GlassesUpdate.State.ERROR_UPDATE_FAIL)); if (this.gVersion.getMajor() < FW_COMPAT) { @@ -219,6 +217,45 @@ private void onCharacteristicError(final BluetoothGattCharacteristic characteris this.onConnectionFail.accept(this.discoveredGlasses); } + private void startUpdate(String strVersion){ + final String latestApiPath = String.format("/firmwares/%s/%s/%s", this.glasses.getDeviceInformation().getHardwareVersion(), this.token, strVersion); + final int bl0 = this.glasses.getDeviceInformation().getBatteryLevel(); + if (bl0 < 10) { // if battery low , waits until the battery is high enough to resume + this.onUpdateError(this.progress.withBatteryLevel(bl0).withStatus(GlassesUpdate.State.ERROR_UPDATE_FAIL_LOW_BATTERY)); + final Runnable resume = () -> this.resumeOnFirmwareHistoryResponse(latestApiPath); + this.glasses.subscribeToBatteryLevelNotifications(bl -> this.onBatteryLevelNotification(resume, bl)); + } else { + this.resumeOnFirmwareHistoryResponse(latestApiPath); + } + } + + private void noUpdateAvailable(){ + Log.d("FW_LATEST", "No firmware update available"); + this.glasses.subscribeToFlowControlNotifications(fc -> { + this.glasses.unsubscribeToFlowControlNotifications(); + this.glasses.cfgRead("ALooK", info -> { + final String gStrVersion = String.format("%d.%d.%d", this.gVersion.getMajor(), this.gVersion.getMinor(), this.gVersion.getPatch()); + + final String cfgHistoryURL = String.format("%s/configurations/%s/%s?compatibility=%d&max-version=%s", + BASE_URL, this.glasses.getDeviceInformation().getHardwareVersion(), this.token, FW_COMPAT, gStrVersion); + + this.requestQueue.add(new JsonObjectRequest( + Request.Method.GET, + cfgHistoryURL, + null, + r -> this.onConfigurationHistoryResponse(r, info), + this::onApiFail + )); + }); + }); + final byte [] fcError = new byte [532]; + fcError[0] = (byte) 0xFF; + this.glasses.writeBytes(fcError); + } + + private boolean isUpdateAvailable(int major,int minor,int patch){ + return (major > this.gVersion.getMajor() || (major == this.gVersion.getMajor() && minor > this.gVersion.getMinor()) || (major == this.gVersion.getMajor() && minor == this.gVersion.getMinor() && patch > this.gVersion.getPatch())); + } void onFirmwareHistoryResponse(final JSONObject jsonObject) { try { final JSONObject latest = jsonObject.getJSONObject("latest"); @@ -229,42 +266,10 @@ void onFirmwareHistoryResponse(final JSONObject jsonObject) { final int patch = lVersion.getInt(2); final String strVersion = String.format("%d.%d.%d", major, minor, patch); this.progress = this.progress.withTargetFirmwareVersion(strVersion); - if ( - major > this.gVersion.getMajor() - || (major == this.gVersion.getMajor() && minor > this.gVersion.getMinor()) - || (major == this.gVersion.getMajor() && minor == this.gVersion.getMinor() && patch > this.gVersion.getPatch()) - ) { - final String latestApiPath = String.format("/firmwares/%s/%s/%s", this.glasses.getDeviceInformation().getHardwareVersion(), this.token, strVersion); - final int bl0 = this.glasses.getDeviceInformation().getBatteryLevel(); - if (bl0 < 10) { - this.onUpdateError(this.progress.withBatteryLevel(bl0).withStatus(GlassesUpdate.State.ERROR_UPDATE_FAIL_LOW_BATTERY)); - final Runnable resume = () -> this.resumeOnFirmwareHistoryResponse(latestApiPath); - this.glasses.subscribeToBatteryLevelNotifications(bl -> this.onBatteryLevelNotification(resume, bl)); - } else { - this.resumeOnFirmwareHistoryResponse(latestApiPath); - } + if (isUpdateAvailable(major,minor,patch)) { + startUpdate(strVersion); } else { - Log.d("FW_LATEST", "No firmware update available"); - this.glasses.subscribeToFlowControlNotifications(fc -> { - this.glasses.unsubscribeToFlowControlNotifications(); - this.glasses.cfgRead("ALooK", info -> { - final String gStrVersion = String.format("%d.%d.%d", this.gVersion.getMajor(), this.gVersion.getMinor(), this.gVersion.getPatch()); - - final String cfgHistoryURL = String.format("%s/configurations/%s/%s?compatibility=%d&max-version=%s", - BASE_URL, this.glasses.getDeviceInformation().getHardwareVersion(), this.token, FW_COMPAT, gStrVersion); - - this.requestQueue.add(new JsonObjectRequest( - Request.Method.GET, - cfgHistoryURL, - null, - r -> this.onConfigurationHistoryResponse(r, info), - this::onApiFail - )); - }); - }); - final byte [] fcError = new byte [532]; - fcError[0] = (byte) 0xFF; - this.glasses.writeBytes(fcError); + noUpdateAvailable(); } } catch (final JSONException e) { this.onApiJSONException(e); diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/DebugSdkSingleton.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/DebugSdkSingleton.java index e870796..4888e77 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/DebugSdkSingleton.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/DebugSdkSingleton.java @@ -18,7 +18,7 @@ public final class DebugSdkSingleton { private static SdkImpl singleton; - public static final synchronized SdkImpl getInstance() { + public static synchronized SdkImpl getInstance() { if (DebugSdkSingleton.singleton == null) { DebugSdkSingleton.singleton = new SdkImpl(); } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/GlassesImpl.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/GlassesImpl.java index 2f3c377..42a0a25 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/GlassesImpl.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/core/debug/GlassesImpl.java @@ -124,7 +124,7 @@ public void subscribeToSensorInterfaceNotifications(Runnable onEvent) { } @Override - public void isWriteWithResponse(boolean on) { + public void setWriteWithResponse(boolean on) { } @Override diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/FontInfo.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/FontInfo.java index 6bd8a57..cbc37e4 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/FontInfo.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/FontInfo.java @@ -35,7 +35,7 @@ public int getHeight() { return this.height; } - public static final List toList(byte[] bytes) { + public static List toList(byte[] bytes) { final ArrayList result = new ArrayList<>(); final PayloadDecoder rp = new PayloadDecoder(bytes); while (rp.hasNext()) { diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageConverter.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageConverter.java index 24f6144..7025c4d 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageConverter.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageConverter.java @@ -21,12 +21,20 @@ public static ImageData getImageData(Bitmap img, ImgSaveFormat fmt) { byte[] encodedImg = getCmd4Bpp(matrix); byte[] cmds = getCmdCompress4BppHeatshrink(encodedImg); return new ImageData(width,cmds, encodedImg.length); + case RG_COLOR_8BPP: + byte[] raw = new byte[width * matrix.length]; + int i = 0; + for (int[] row : matrix) for (int px : row) raw[i++] = (byte) px; + byte[] compressed = getCmdCompress4BppHeatshrink(raw); + return new ImageData(width, compressed, raw.length); default: Log.d("imageFormat", "Unknown format"); } return new ImageData(); } + + public static Image1bppData getImage1bppData(Bitmap img, ImgSaveFormat fmt) { int[][] matrix = convert(img, fmt); int width = matrix[0].length; @@ -75,6 +83,7 @@ private static int[][] convert(Bitmap img, ImgSaveFormat fmt) { return ImageMDP05.convert1Bpp(img); case MONO_4BPP:case MONO_4BPP_HEATSHRINK:case MONO_4BPP_HEATSHRINK_SAVE_COMP: return ImageMDP05.convertDefault(img); + default: Log.d("imageConvert", "Unknown format"); } @@ -184,7 +193,7 @@ private static int getArraySize(int[][] matrix){ return arraySize; } - private static byte[] getCmdCompress4BppHeatshrink(byte[] encodedImg){ + public static byte[] getCmdCompress4BppHeatshrink(byte[] encodedImg){ int windowSize = 8; int lookaheadSize = 4; ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageMDP05.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageMDP05.java index 541df4c..c6c6d98 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageMDP05.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImageMDP05.java @@ -15,10 +15,13 @@ public static int[][] convertDefault(Bitmap img){ int[][] encodedImg = new int[height][width]; + int[] pixels = new int[width * height]; + img.getPixels(pixels, 0, width, 0, 0, width, height); // modifier pour récupérer les pixels en batch plutôt qu'un par un + //reduce to 4bpp for (int y=0; y < height; y++){ for (int x=0; x < width; x++){ - int pxl = rgbTo8bitGrayWeightedConvertion(img.getPixel(x,y)); + int pxl = rgbTo8bitGrayWeightedConvertion(pixels[y * width + x]); //convert gray8bit to gray4bit encodedImg[y][x] = Math.round(pxl/16); } @@ -35,11 +38,15 @@ public static int[][] convert1Bpp(Bitmap img) { int[][] encodedImg = new int[height][width]; - //reduce to 1 bpp + int[] pixels = new int[width * height]; + img.getPixels(pixels, 0, width, 0, 0, width, height); // modifier pour récupérer les pixels en batch plutôt qu'un par un + + + //reduce to 1 bpp for (int y=0; y < height; y++){ for (int x=0; x < width; x++){ //convert gray8bit in gray1bit - if (rgbTo8bitGrayWeightedConvertion(img.getPixel(x,y)) > 0){ + if ((rgbTo8bitGrayWeightedConvertion(pixels[y * width + x])) > 0){ encodedImg[y][x] = 1; }else{ encodedImg[y][x] = 0; diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImgSaveFormat.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImgSaveFormat.java index f49cd6e..7f604b5 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImgSaveFormat.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/ImgSaveFormat.java @@ -39,4 +39,12 @@ public byte[] toBytes() { return new byte[]{(byte) 0x03}; } }, + RG_COLOR_8BPP { + @Override + public byte[] toBytes() { + return new byte[]{(byte) 0x0A}; + } + }, + + } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PageInfo.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PageInfo.java index 3ce1eb5..b98cfae 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PageInfo.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PageInfo.java @@ -16,8 +16,7 @@ import com.activelook.activelooksdk.core.Payload; -import java.util.ArrayList; -import java.util.List; + public class PageInfo { diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PayloadDecoder.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PayloadDecoder.java index 8253b85..a29fe66 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PayloadDecoder.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/PayloadDecoder.java @@ -24,24 +24,24 @@ final class PayloadDecoder { this.offset = 0; } - final boolean hasNext() { + boolean hasNext() { return this.offset < this.bytes.length; } - final byte readByte() { + byte readByte() { return this.bytes[this.offset++]; } - final byte readByte(final int offset) { + byte readByte(final int offset) { this.offset = offset; return this.readByte(); } - final long readLong() { + long readLong() { return this.readLong(8); } - final long readLong(final int size) { + long readLong(final int size) { long result = 0l; final int end = size + this.offset; while (this.offset < end) { @@ -51,56 +51,56 @@ final long readLong(final int size) { return result; } - final long readLong(final int offset, final int size) { + long readLong(final int offset, final int size) { this.offset = offset; return this.readLong(size); } - final char readChar() { + char readChar() { return (char) this.readLong(2); } - final char readChar(final int size) { + char readChar(final int size) { return (char) this.readLong(size); } - final char readChar(final int offset, final int size) { + char readChar(final int offset, final int size) { return (char) this.readLong(offset, size); } - final short readShort() { + short readShort() { return (short) this.readLong(2); } - final short readShort(final int size) { + short readShort(final int size) { return (short) this.readLong(size); } - final short readShort(final int offset, final int size) { + short readShort(final int offset, final int size) { return (short) this.readLong(offset, size); } - final int readUInt() { + int readUInt() { return (int) this.readLong(4); } - final int readUInt(final int size) { + int readUInt(final int size) { return (int) this.readLong(size); } - final int readUInt(final int offset, final int size) { + int readUInt(final int offset, final int size) { return (int) this.readLong(offset, size); } - final boolean readBoolean() { + boolean readBoolean() { return this.readLong(1) != 0; } - final boolean readBoolean(final int size) { + boolean readBoolean(final int size) { return this.readLong(size) != 0; } - final boolean readBoolean(final int offset, final int size) { + boolean readBoolean(final int offset, final int size) { return this.readLong(offset, size) != 0; } diff --git a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/Utils.java b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/Utils.java index a2f0b7c..d69fde8 100644 --- a/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/Utils.java +++ b/ActiveLookSDK/src/main/java/com/activelook/activelooksdk/types/Utils.java @@ -27,17 +27,17 @@ public final class Utils { * * @throws InvalidParameterException If value if not between 0 and 15. */ - public static final byte greyLevel(int value) { + public static byte greyLevel(int value) { Utils.constraints(value, 0, 15); return (byte) (value & 0xFF); } - public static final short toSignedCoordinate(int value) { + public static short toSignedCoordinate(int value) { Utils.constraints(value, -512, 512); return (short) value; } - public static final Rotation toRotation(byte value) { + public static Rotation toRotation(byte value) { switch (value) { case 0x00: return Rotation.BOTTOM_RL;