diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..affc627 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +*.log +*.list +tools_python/frames.txt +*.prefs +*.exe diff --git a/PriceHax/.gitignore b/PriceHax/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/PriceHax/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/PriceHax/app/.gitignore b/PriceHax/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/PriceHax/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/PriceHax/app/build.gradle b/PriceHax/app/build.gradle new file mode 100644 index 0000000..8a4aeb3 --- /dev/null +++ b/PriceHax/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'org.furrtek.pricehax' + compileSdk 32 + + defaultConfig { + applicationId "org.furrtek.pricehax" + minSdk 24 + targetSdk 32 + versionCode 2 + versionName "2.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.navigation:navigation-fragment:2.4.1' + implementation 'androidx.navigation:navigation-ui:2.4.1' + implementation files('jnilibs/zbar.jar') + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'com.github.mik3y:usb-serial-for-android:3.4.6' +} \ No newline at end of file diff --git a/PriceHax/app/jnilibs/arm64-v8a/libiconv.so b/PriceHax/app/jnilibs/arm64-v8a/libiconv.so new file mode 100644 index 0000000..31c8e4f Binary files /dev/null and b/PriceHax/app/jnilibs/arm64-v8a/libiconv.so differ diff --git a/PriceHax/app/jnilibs/arm64-v8a/libzbarjni.so b/PriceHax/app/jnilibs/arm64-v8a/libzbarjni.so new file mode 100644 index 0000000..9e0ce49 Binary files /dev/null and b/PriceHax/app/jnilibs/arm64-v8a/libzbarjni.so differ diff --git a/PriceHax/app/jnilibs/armeabi-v7a/libiconv.so b/PriceHax/app/jnilibs/armeabi-v7a/libiconv.so new file mode 100644 index 0000000..2bcbb70 Binary files /dev/null and b/PriceHax/app/jnilibs/armeabi-v7a/libiconv.so differ diff --git a/PriceHax/app/jnilibs/armeabi-v7a/libzbarjni.so b/PriceHax/app/jnilibs/armeabi-v7a/libzbarjni.so new file mode 100644 index 0000000..2693dbb Binary files /dev/null and b/PriceHax/app/jnilibs/armeabi-v7a/libzbarjni.so differ diff --git a/PriceHax/app/jnilibs/armeabi/libiconv.so b/PriceHax/app/jnilibs/armeabi/libiconv.so new file mode 100644 index 0000000..9c7150d Binary files /dev/null and b/PriceHax/app/jnilibs/armeabi/libiconv.so differ diff --git a/PriceHax/app/jnilibs/armeabi/libzbarjni.so b/PriceHax/app/jnilibs/armeabi/libzbarjni.so new file mode 100644 index 0000000..3d4a8ac Binary files /dev/null and b/PriceHax/app/jnilibs/armeabi/libzbarjni.so differ diff --git a/PriceHax/app/jnilibs/x86/libiconv.so b/PriceHax/app/jnilibs/x86/libiconv.so new file mode 100644 index 0000000..6ab43e5 Binary files /dev/null and b/PriceHax/app/jnilibs/x86/libiconv.so differ diff --git a/PriceHax/app/jnilibs/x86/libzbarjni.so b/PriceHax/app/jnilibs/x86/libzbarjni.so new file mode 100644 index 0000000..d64f517 Binary files /dev/null and b/PriceHax/app/jnilibs/x86/libzbarjni.so differ diff --git a/PriceHax/app/jnilibs/zbar.jar b/PriceHax/app/jnilibs/zbar.jar new file mode 100644 index 0000000..7d50b95 Binary files /dev/null and b/PriceHax/app/jnilibs/zbar.jar differ diff --git a/PriceHax/app/proguard-rules.pro b/PriceHax/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/PriceHax/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/PriceHax/app/src/main/AndroidManifest.xml b/PriceHax/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..690d7a5 --- /dev/null +++ b/PriceHax/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponse.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponse.java new file mode 100644 index 0000000..e0c87d4 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponse.java @@ -0,0 +1,6 @@ +package org.furrtek.pricehax; + +public interface AsyncResponse { + //Add requestCode to identify request. + public void processFinish(DMImage dmimage); +} diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponseTX.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponseTX.java new file mode 100644 index 0000000..0271499 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/AsyncResponseTX.java @@ -0,0 +1,6 @@ +package org.furrtek.pricehax; + +public interface AsyncResponseTX { + //Add requestCode to identify request. + public void processFinish(boolean result); +} diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/CameraPreview.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/CameraPreview.java new file mode 100644 index 0000000..37f8298 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/CameraPreview.java @@ -0,0 +1,153 @@ +package org.furrtek.pricehax; + +import android.app.Activity; +import android.content.Context; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.AutoFocusCallback; +import android.hardware.Camera.PreviewCallback; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; +import android.view.SurfaceView; + +import java.io.IOException; + +import android.content.res.Configuration; + +public class CameraPreview extends SurfaceView implements Callback { + private AutoFocusCallback autoFocusCallback; + private Camera mCamera; + private SurfaceHolder mHolder = getHolder(); + private PreviewCallback previewCallback; + private boolean oldAutoFocusMode; + private Context context; + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + //setCameraDisplayOrientation((Activity) this.context, this.mCamera); + } + + //public CameraPreview(Context context, Camera camera, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) { + public CameraPreview(Context context, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) { + super(context); + this.context = context; + + this.mCamera = getBackCamera(); + this.previewCallback = previewCb; + this.autoFocusCallback = autoFocusCb; + this.mHolder.addCallback(this); + //this.mHolder.setType(3); + + //this.cameraId = getBackCameraId(); + + // The old mode uses the onAutoFocus function and makes a timer to try to focus periodically. + // The drawback is the hunting of the camera. + // This can be "smoothed" by using either FOCUS_MODE_CONTINUOUS_PICTURE or FOCUS_MODE_CONTINUOUS_VIDEO parameters instead (if available). + Camera.Parameters params = this.mCamera.getParameters(); + + this.oldAutoFocusMode = false; + if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + } + else if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); + } + else { + this.oldAutoFocusMode = true; + } + + this.mCamera.setParameters(params); + //this.mCamera.setPreviewCallback(previewCb); + this.mCamera.startPreview(); + } + + public boolean isOldAutoFocusMode() { + return this.oldAutoFocusMode; + } + + public void surfaceCreated(SurfaceHolder holder) { + try { + if (this.mCamera != null) { + this.mCamera.setPreviewDisplay(holder); + } + } catch (IOException e) { + Log.d("DBG", "Error setting camera preview: " + e.getMessage()); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (this.mHolder.getSurface() != null) { + try { + this.mCamera.stopPreview(); + } catch (Exception e) { + } + try { + setCameraDisplayOrientation((Activity) this.context, this.mCamera, width, height); + + this.mCamera.setPreviewDisplay(this.mHolder); + this.mCamera.setPreviewCallback(this.previewCallback); + this.mCamera.startPreview(); + + if (this.oldAutoFocusMode) { + this.mCamera.autoFocus(this.autoFocusCallback); + } + + } catch (Exception e2) { + Log.d("DBG", "Error starting camera preview: " + e2.getMessage()); + } + } + } + + private Camera getBackCamera() { + int numberOfCameras = Camera.getNumberOfCameras(); + CameraInfo cameraInfo = new CameraInfo(); + for (int i = 0; i < numberOfCameras; i++) { + Camera.getCameraInfo(i, cameraInfo); + if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { + return Camera.open(i); + } + } + return null; + } + + // Sample code from Android Developer documentation + // https://developer.android.com/reference/android/hardware/Camera#setDisplayOrientation%28int%29 + public static void setCameraDisplayOrientation(Activity activity, android.hardware.Camera camera, int width, int height) { + //android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); + + Camera.Parameters params = camera.getParameters(); + int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); + int degrees = 0; + + switch (rotation) { + case Surface.ROTATION_0: + degrees = 90; + //params.setPreviewSize(height, width); + break; + case Surface.ROTATION_90: degrees = 0; break; + case Surface.ROTATION_180: degrees = 0; break; + case Surface.ROTATION_270: degrees = 180; break; + } + Log.d("DBG", "Preview surface rotation: " + degrees); + + int result; + result = degrees; + /*if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + result = (info.orientation + degrees) % 360; + result = (360 - result) % 360; // compensate the mirror + } else { // back-facing + result = (info.orientation - degrees + 360) % 360; + }*/ + Camera.Size size = params.getPreviewSize(); + Log.d("DBG", "getPreviewSize: " + size.width + "," + size.height); + + camera.setDisplayOrientation(result); + camera.setParameters(params); + } +} diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/DMConvert.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMConvert.java new file mode 100644 index 0000000..f5565a7 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMConvert.java @@ -0,0 +1,185 @@ +package org.furrtek.pricehax; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ProgressBar; +import android.widget.ImageView; +import android.graphics.Color; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import static org.furrtek.pricehax.DitherBitmap.floydSteinbergDithering; + +public class DMConvert extends AsyncTask { + private ImageView mImageView; + private ProgressBar mProgressBar; + AsyncResponse delegate = null; + + DMConvert(ProgressBar progressBar, AsyncResponse asyncResponse) { + delegate = asyncResponse; + mProgressBar = progressBar; + } + @Override + protected void onProgressUpdate(Integer... progress) { + mProgressBar.setProgress(progress[0]); + } + @Override + protected void onPostExecute(DMImage dmimage) { + Log.d("PHX", "Convert done, bytestream size: " + dmimage.byteStreamBW.size()); + delegate.processFinish(dmimage); + } + @Override + protected DMImage doInBackground(Bitmap... selectedImage) { + int x, y, wi, hi; + int w, h; + int pixel; + int idx = 0; + Bitmap imageBW, imageBWR; + w = selectedImage[0].getWidth(); + h = selectedImage[0].getHeight(); + DMImage dmimage = new DMImage(w, h); + + /*if (PLType == 1318) { + w = 0xD0; + h = 0x70; + } else { + w = 172; + h = 72; + }*/ + wi = w; + hi = h; + + BitSet bitstreamBW = new BitSet(wi * hi); + BitSet bitstreamBWR = new BitSet(wi * hi * 2); + + Bitmap scaledimage = selectedImage[0]; + + //Convert to BW + Log.d("PHX", "Convert to BW"); + imageBW = floydSteinbergDithering(scaledimage, false); + //dmimage.bitmapBW = imageBW; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++, idx++) { + pixel = imageBW.getPixel(x, y); + if (Color.red(pixel) > 0) + bitstreamBW.set(idx); + else + bitstreamBW.clear(idx); + dmimage.bitmapBW.setPixel(x, y, pixel); + } + } + + //Convert to BWR + Log.d("PHX", "Convert to BWR"); + imageBWR = floydSteinbergDithering(scaledimage, true); + //dmimage.bitmapBWR = imageBWR; + idx = 0; + int idxr = w * h; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++, idx++, idxr++) { + pixel = imageBWR.getPixel(x, y); + if (Color.red(pixel) > 0) { + if (Color.green(pixel) > 0) { + bitstreamBWR.set(idx); // White + bitstreamBWR.set(idxr); + dmimage.bitmapBWR.setPixel(x, y, 0xFFFFFFFF); + } else { + bitstreamBWR.clear(idx); // Red + bitstreamBWR.clear(idxr); + dmimage.bitmapBWR.setPixel(x, y, 0xFFFF0000); + } + } else { + bitstreamBWR.clear(idx); // Black + bitstreamBWR.set(idxr); + dmimage.bitmapBWR.setPixel(x, y, 0xFF000000); + } + } + } + + BitSet bitstreamBW_RLE = RLECompress(bitstreamBW); + BitSet bitstreamBWR_RLE = RLECompress(bitstreamBWR); + + publishProgress(90); + + dmimage.byteStreamBW = bitstreamBytes(bitstreamBW_RLE); + dmimage.byteStreamBWR = bitstreamBytes(bitstreamBWR_RLE); + + publishProgress(100); + + return dmimage; + } + + private List bitstreamBytes(BitSet bitstream) { + List result = new ArrayList(); + byte[] bytes = bitstream.toByteArray(); + for (byte b : bytes) { + int x = 0; + for (int c = 0; c < 8; c++) { + x >>= 1; + if (b < 0) + x |= 0x80; + b <<= 1; + } + result.add((byte)x); + } + int idx = result.size(); + int klp = idx % 20; + if (klp > 0) klp = 20 - klp; + Log.d("PHX", String.format("Padding %d to %d", idx, idx + klp)); + for (int bsc = 0; bsc < klp; bsc++) + result.add(new Byte((byte)0)); + + return result; + } + + private BitSet RLECompress(BitSet bitstream) { + int j = bitstream.length() - 1; + int cnt = 1; + boolean p = bitstream.get(0); + boolean n; + List RLErun = new ArrayList(); + + // RLE compress + Log.d("PHX", String.format("RLE compress, raw size = %d", bitstream.length())); + for (int m = 1; m <= j; m++) { + n = bitstream.get(m); + if (n == p) { + cnt++; + } else { + RLErun.add(cnt); + cnt = 1; + p = n; + } + } + if (cnt > 1) RLErun.add(Integer.valueOf(cnt)); + + String bs; + BitSet bitstreamRLE = new BitSet(); + bitstreamRLE.set(0, bitstream.get(0)); + + Log.d("PHX", "Gen unary coded runs"); + int idx = 1; + for (int cnts : RLErun) { + bs = Integer.toBinaryString(cnts); + for (int bsc = 0; bsc < bs.length() - 1; bsc++) { + bitstreamRLE.clear(idx++); + } + for (int bsc = 0; bsc < bs.length(); bsc++) { + if (bs.charAt(bsc) == '1') + bitstreamRLE.set(idx++); + else + bitstreamRLE.clear(idx++); + } + } + + String dbg = ""; + for (int i = 0; i < 256; i++) { + dbg += (bitstreamRLE.get(i) ? "1" : "0"); + } + Log.d("PHX", dbg); + return bitstreamRLE; + } +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/DMGen.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMGen.java new file mode 100644 index 0000000..43ddb2d --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMGen.java @@ -0,0 +1,133 @@ +package org.furrtek.pricehax; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ProgressBar; +import android.widget.ImageView; +import android.graphics.Color; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +import static org.furrtek.pricehax.DitherBitmap.floydSteinbergDithering; + +public class DMGen { + public static List DMGenFrames(DMImage dmImage, boolean BWR, Long PLID, int dispPage) { + List frames = new ArrayList(); + + // Wake-up frame + Byte[] payload = {(byte) 0x17, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload), 30, 200)); // TODO: Check delay and repeats + + int datalen = dmImage.byteStreamBW.size(); // TODO: Pass selected BW or BWR bytestream instead of dmImage + int width = dmImage.bitmapBW.getWidth(); + int height = dmImage.bitmapBW.getHeight(); + Log.d("PHX", String.format("w,h: %d,%d", width, height)); + int x = 0; + int y = 0; + Byte[] payload_start = { + (byte) 0x34, 0, 0, 0, 0x05, + (byte) (datalen >> 8), (byte) (datalen & 255), + 0, + 0x02, + (byte) dispPage, //0x01, + (byte) (width >> 8), (byte) (width & 255), + (byte) (height >> 8), (byte) (height & 255), + (byte) (x >> 8), (byte) (x & 255), + (byte) (y >> 8), (byte) (y & 255), + 0x00, 0x00, + (byte) 0x88, + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_start), 30, 1)); // TODO: Check delay and repeats + + /*ymax = datalen / 40; // WAS 20 + + for (y = 0; y < datalen / 40; y++) { // WAS 20 + + startcode[0] = (byte) 0x85; + startcode[1] = (byte) (plID & 255); + startcode[2] = (byte) (plID >> 8); + startcode[3] = (byte) (plID >> 16); + startcode[4] = (byte) (plID >> 24); + startcode[5] = (byte) 0x34; + startcode[6] = (byte) 0x00; + startcode[7] = (byte) 0x00; + startcode[8] = (byte) 0x00; + startcode[9] = (byte) 0x20; + startcode[10] = (byte) (y >> 16); + startcode[11] = (byte) (y & 255); + + for (int cp = 0; cp < 40; cp++) { // WAS 20 + startcode[12 + cp] = hexlist.get(cp + (y * 40)); // WAS 20 + } + + FrameCRC = CRCCalc.GetCRC(startcode, 52); // WAS 32 + startcode[52] = FrameCRC[0]; // WAS 32 + startcode[53] = FrameCRC[1]; // WAS 33 + + // Send ! + PP4C.sendPP4C(at.getApplicationContext(), startcode, 54, donglever, 1, audioTrack); // WAS 34 + }*/ + + Log.d("PHX", String.format("Datalen %d", datalen)); + int ymax = (int) Math.ceil((double)datalen / 20); + Log.d("PHX", String.format("ymax %d", ymax)); + for (y = 0; y < ymax; y++) { + Byte[] payload_data = new Byte[27]; + Log.d("PHX", String.format("Gen data frame %d", y)); + /* (byte) 0x34, 0, 0, 0, 0x20, (byte) (y >> 8), (byte) (y & 255)*/ + payload_data[0] = (byte) 0x34; + payload_data[1] = (byte) 0; + payload_data[2] = (byte) 0; + payload_data[3] = (byte) 0; + payload_data[4] = (byte) 0x20; + payload_data[5] = (byte) (y >> 8); + payload_data[6] = (byte) (y & 255); + for (int cp = 0; cp < 20; cp++) { // WAS 20 + payload_data[7 + cp] = dmImage.byteStreamBW.get(cp + (y * 20)); + } + frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payload_data), 30, 1)); // TODO: Check delay and repeats + } + + /* + byte[] vercode = {(byte) 0x85, 0, 0, 0, 0, 0x34, 00, 00, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00}; + + vercode[1] = (byte) (plID & 255); + vercode[2] = (byte) (plID >> 8); + vercode[3] = (byte) (plID >> 16); + vercode[4] = (byte) (plID >> 24); + + FrameCRC = CRCCalc.GetCRC(vercode, 28); + vercode[28] = FrameCRC[0]; + vercode[29] = FrameCRC[1]; + + runOnUiThread(new Runnable() { + @Override + public void run() { + txtworkh.setText("Verify frame..."); + } + }); + + // Send ! + PP4C.sendPP4C(at.getApplicationContext(), vercode, 30, donglever, 10, audioTrack); + + SystemClock.sleep(2000); + + runOnUiThread(new Runnable() { + @Override + public void run() { + txtworkh.setText("Done ! ;-)"); + } + });*/ + + Byte[] payloadc = {(byte) 0x34, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + frames.add(new IRFrame(PLID, (byte)0x85, Arrays.asList(payloadc), 30, 1)); // TODO: Check delay and repeats + + return frames; + } +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/DMImage.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMImage.java new file mode 100644 index 0000000..0f9d20f --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/DMImage.java @@ -0,0 +1,18 @@ +package org.furrtek.pricehax; + +import android.graphics.Bitmap; + +import java.util.ArrayList; +import java.util.List; + +public class DMImage { + Bitmap bitmapBW; + Bitmap bitmapBWR; + List byteStreamBW = new ArrayList(); + List byteStreamBWR = new ArrayList(); + + public DMImage(int width, int height) { + this.bitmapBW = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + this.bitmapBWR = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } +} diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/DitherBitmap.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/DitherBitmap.java new file mode 100644 index 0000000..f3b5b00 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/DitherBitmap.java @@ -0,0 +1,95 @@ +package org.furrtek.pricehax; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.util.Log; + +public class DitherBitmap { + // Atkinson dither might give better results on ESLs + public static Bitmap floydSteinbergDithering(Bitmap sourceimg, boolean withRed) { + + Bitmap img = sourceimg.copy(sourceimg.getConfig(), true); + + C3[] paletteBW = new C3[] { + new C3(0, 0, 0), + new C3(255, 255, 255) + }; + C3[] paletteBWR = new C3[] { + new C3(0, 0, 0), + new C3(255, 0, 0), + new C3(255, 255, 255) + }; + + int w = sourceimg.getWidth(); + int h = sourceimg.getHeight(); + + C3[][] d = new C3[h][w]; + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + d[y][x] = new C3(sourceimg.getPixel(x, y)); + + for (int y = 0; y < img.getHeight(); y++) { + for (int x = 0; x < sourceimg.getWidth(); x++) { + + C3 oldColor = d[y][x]; + C3 newColor = findClosestPaletteColor(oldColor, withRed ? paletteBWR : paletteBW); + img.setPixel(x, y, newColor.toARGB()); + + C3 err = oldColor.sub(newColor); + + if (x+1 < w) d[y ][x+1] = d[y ][x+1].add(err.mul(7.f/16)); + if (x-1>=0 && y+1, TXProgress, Boolean> { + private ImageView mImageView; + private ProgressBar mProgressBar; + AsyncResponseTX delegate = null; + UsbSerialPort usbSerialPort; + TextView mTextView; + TXProgress progress = new TXProgress(0, ""); + + ESLBlaster(UsbSerialPort usbserialport, ProgressBar progressBar, TextView textView, AsyncResponseTX asyncResponse) { + delegate = asyncResponse; + mProgressBar = progressBar; + mTextView = textView; + usbSerialPort = usbserialport; + } + @Override + protected void onProgressUpdate(TXProgress... progress) { + //Log.d("PHX", "Progress: " + progress[0]); + mTextView.setText(progress[0].text); + mProgressBar.setProgress(progress[0].percent); + } + @Override + protected void onPostExecute(Boolean result) { + Log.d("PHX", "TX done"); + endTX(); + delegate.processFinish(result); + } + @Override + protected Boolean doInBackground(List... frames) { + int i = 1; + int cnt = frames[0].size(); + for (IRFrame frame : frames[0]) { + /*try { + usbSerialPort.purgeHwBuffers(true, true); + } catch(IOException e) { + e.printStackTrace(); + return false; + }*/ + + List list = frame.getRawData(false); + + byte[] data = new byte[list.size() + 5 + 1]; + data[0] = (byte)'L'; + data[1] = (byte)list.size(); + data[2] = (byte)frame.delay; // Delay between repeats + data[3] = (byte)(frame.repeats & 255); // Number of repeats + data[4] = (byte)(frame.repeats >> 8); + for (int c = 0; c < list.size(); c++) + data[c + 5] = list.get(c).byteValue(); + data[data.length - 1] = (byte)'T'; + + // Debug + String hex_str = "Blaster frame " + String.format("%d/%d: ", i, cnt); + for (byte b : data) + hex_str += String.format("%02X ", b); + Log.d("PHX", hex_str); + try { + byte[] buffer = new byte[256]; + usbSerialPort.write(data, 1000); + if (frame.repeats > 10) { + // Differentiate data vs. wake up frames with their repeat count + progress.text = "Waking up ESL, please wait..."; + publishProgress(progress); + } else { + progress.text = String.format("Transmitting frame %d/%d...", i, cnt); + publishProgress(progress); + } + + // 10s timeout should be enough + // Split the timeout to check if task was cancelled during the wait + int w = 0; + for (w = 0; w < 10; w++) { + if (isCancelled()) { + endTX(); + return false; + } + if (usbSerialPort.read(buffer, 1000) > 0) { + //Log.d("PHX", String.valueOf((char)buffer[0])); + if (buffer[0] == (byte)'K') + break; + }; + } + if (w == 10) { + endTX(); // Timed out + return false; + } + } catch(IOException e) { + e.printStackTrace(); + endTX(); + return false; + } + progress.percent = (int)((i * 100) / cnt); + publishProgress(progress); + i++; + } + endTX(); + return true; + } + + private void endTX() { + progress.percent = 0; + progress.text = "Ready"; + publishProgress(progress); + } +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.bak b/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.bak new file mode 100644 index 0000000..3714619 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.bak @@ -0,0 +1,253 @@ +package org.furrtek.pricehax; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.Build; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.content.Intent; +import android.content.Context; +import android.app.PendingIntent; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; +import com.google.android.material.snackbar.Snackbar; +import org.furrtek.pricehax.databinding.FragmentFirstBinding; +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialPort; +import com.hoho.android.usbserial.driver.UsbSerialProber; +import com.hoho.android.usbserial.util.SerialInputOutputManager; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class FirstFragment extends Fragment { + + private enum UsbPermission { Unknown, Requested, Granted, Denied } + private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; + + private FragmentFirstBinding binding; + private int deviceId, portNum, baudRate; + private UsbSerialPort usbSerialPort; + private UsbPermission usbPermission = UsbPermission.Unknown; + private SerialInputOutputManager usbIoManager; + private boolean connected = false; + private static final int SELECT_IMAGE = 100; + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) { + super.onActivityResult(requestCode, resultCode, imageReturnedIntent); + ImageView imgbmp; + Bitmap scaledimage; + + if (requestCode == SELECT_IMAGE) { + if (resultCode == Activity.RESULT_OK) { + if (imageReturnedIntent.getData() != null) { + try { + Uri selectedImage = imageReturnedIntent.getData(); + Object imageStream = getContentResolver().openInputStream(selectedImage); + imgbmp = (ImageView) findViewById(R.id.imageview_dm); + Bitmap image = BitmapFactory.decodeStream(imageStream); + scaledimage = Bitmap.createScaledBitmap(image, wi, hi, true); + imgbmp.setImageBitmap(scaledimage); + } catch (Exception e) { + Log.d("PHX", e.getLocalizedMessage()); + } + } + } + } + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState + ) { + + binding = FragmentFirstBinding.inflate(inflater, container, false); + return binding.getRoot(); + + } + + public byte[] Transmit(List list) { + byte[] data = new byte[list.size() + 5 + 1]; + data[0] = 76; + data[1] = (byte)list.size(); + data[2] = 30; + data[3] = 100; + data[4] = 0; + for(int i = 0; i < list.size(); i++) { + data[i + 5] = list.get(i).byteValue(); + } + data[data.length - 1] = 84; + + // Debug + String hex_str = ""; + for (byte b : data) { + hex_str += String.format("%02X ", b); + } + + Log.d("TX", hex_str); + + return data; + } + + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + binding.buttonFirst.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + testconnect(view); + } + }); + + binding.buttonTxA.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //byte data[] = {76, 13, 30, 100, 0, (byte)0x85, 0x00, 0x00, 0x00, 0x00, 0x06, (byte)0xF1, 0x00, 0x00, 0x00, 0x0A, 0x5D, 0x14, 84}; + + IRFrame frame = new IRFrame(); + frame.PLID = 0; + frame.protocol = (byte)0x85; + Byte[] pl = {0x06, (byte)0x03, 0x00, 0x00}; + + EditText text = (EditText)getView().findViewById(R.id.customValue); + byte b = (byte)Integer.parseInt(text.getText().toString(), 16); + pl[1] = b; + + frame.payload = Arrays.asList(pl); + + byte[] data = FirstFragment.this.Transmit(frame.getRawData(false)); + + try { + if (connected) + usbSerialPort.write(data, 1000); + } + catch(IOException e) { + e.printStackTrace(); + } + } + }); + + binding.buttonTxB.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + byte data[] = {76, 13, 30, 100, 0, (byte)0x85, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x00, 0x00, 0x00, 0x01, 0x08, 0x6F, 84}; + try { + if (connected) + usbSerialPort.write(data, 1000); + } + catch(IOException e) { + e.printStackTrace(); + } + } + }); + + binding.buttonLoadImg.setOnLongClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + photoPickerIntent.setType("image/*"); + photoPickerIntent.setAction(Intent.ACTION_GET_CONTENT); + startActivityForResult(photoPickerIntent, SELECT_IMAGE); + } + }); + } + + public void status(View view, String msg) { + Snackbar.make(view, msg, Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } + + public void testconnect(View view) { + /*NavHostFragment.findNavController(FirstFragment.this) + .navigate(R.id.action_FirstFragment_to_SecondFragment);*/ + UsbDevice device = null; + UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); + /*for(UsbDevice v : usbManager.getDeviceList().values()) + if(v.getDeviceId() == deviceId) + device = v;*/ + Collection devices = usbManager.getDeviceList().values(); + if (devices.size() == 0) + return; + device = devices.iterator().next(); + if(device == null) { + status(view,"connection failed: device not found"); + return; + } + UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if(driver == null) { + //status(view,"connection failed: no driver for device"); + //status(view, String.valueOf(device.getVendorId())); // 1155 = 0x483 ok + status(view, String.valueOf(device.getProductId())); + return; + } + if(driver.getPorts().size() < portNum) { + status(view,"connection failed: not enough ports at device"); + return; + } + usbSerialPort = driver.getPorts().get(portNum); + UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); + if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { + usbPermission = UsbPermission.Requested; + int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; + PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), flags); + usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); + return; + } + if(usbConnection == null) { + if (!usbManager.hasPermission(driver.getDevice())) + status(view,"connection failed: permission denied"); + else + status(view,"connection failed: open failed"); + return; + } + + try { + usbSerialPort.open(usbConnection); + usbSerialPort.setParameters(57600, 8, 1, UsbSerialPort.PARITY_NONE); + //if(withIoManager) { + // usbIoManager = new SerialInputOutputManager(usbSerialPort, this); + // usbIoManager.start(); + //} + status(view,"connected"); + + connected = true; + } catch (Exception e) { + status(view,"connection failed: " + e.getMessage()); + disconnect(); + } + } + + private void disconnect() { + connected = false; + if(usbIoManager != null) { + usbIoManager.setListener(null); + usbIoManager.stop(); + } + usbIoManager = null; + try { + usbSerialPort.close(); + } catch (IOException ignored) {} + usbSerialPort = null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.java new file mode 100644 index 0000000..966e074 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/FirstFragment.java @@ -0,0 +1,212 @@ +package org.furrtek.pricehax; + +import android.os.Bundle; +import android.os.Build; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.content.Intent; +import android.content.Context; +import android.app.PendingIntent; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.widget.EditText; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; +import com.google.android.material.snackbar.Snackbar; +import org.furrtek.pricehax.databinding.FragmentFirstBinding; +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialPort; +import com.hoho.android.usbserial.driver.UsbSerialProber; +import com.hoho.android.usbserial.util.SerialInputOutputManager; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class FirstFragment extends Fragment { + + private enum UsbPermission { Unknown, Requested, Granted, Denied } + private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; + + private FragmentFirstBinding binding; + private int deviceId, portNum, baudRate; + private UsbSerialPort usbSerialPort; + private UsbPermission usbPermission = UsbPermission.Unknown; + private SerialInputOutputManager usbIoManager; + private boolean connected = false; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState + ) { + + binding = FragmentFirstBinding.inflate(inflater, container, false); + return binding.getRoot(); + + } + + public byte[] Transmit(List list) { + byte[] data = new byte[list.size() + 5 + 1]; + data[0] = 76; + data[1] = (byte)list.size(); + data[2] = 30; + data[3] = 100; + data[4] = 0; + for(int i = 0; i < list.size(); i++) { + data[i + 5] = list.get(i).byteValue(); + } + data[data.length - 1] = 84; + + // Debug + String hex_str = ""; + for (byte b : data) { + hex_str += String.format("%02X ", b); + } + + Log.d("TX", hex_str); + + return data; + } + + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + binding.buttonFirst.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + testconnect(view); + } + }); + + binding.buttonTxA.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //byte data[] = {76, 13, 30, 100, 0, (byte)0x85, 0x00, 0x00, 0x00, 0x00, 0x06, (byte)0xF1, 0x00, 0x00, 0x00, 0x0A, 0x5D, 0x14, 84}; + + IRFrame frame = new IRFrame(); + frame.PLID = 0; + frame.protocol = (byte)0x85; + Byte[] pl = {0x06, (byte)0x03, 0x00, 0x00}; + + EditText text = (EditText)getView().findViewById(R.id.customValue); + byte b = (byte)Integer.parseInt(text.getText().toString(), 16); + pl[1] = b; + + frame.payload = Arrays.asList(pl); + + byte[] data = FirstFragment.this.Transmit(frame.getRawData(false)); + + try { + if (connected) + usbSerialPort.write(data, 1000); + } + catch(IOException e) { + e.printStackTrace(); + } + } + }); + + binding.buttonTxB.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + byte data[] = {76, 13, 30, 100, 0, (byte)0x85, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x00, 0x00, 0x00, 0x01, 0x08, 0x6F, 84}; + try { + if (connected) + usbSerialPort.write(data, 1000); + } + catch(IOException e) { + e.printStackTrace(); + } + } + }); + } + + public void status(View view, String msg) { + Snackbar.make(view, msg, Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } + + public void testconnect(View view) { + /*NavHostFragment.findNavController(FirstFragment.this) + .navigate(R.id.action_FirstFragment_to_SecondFragment);*/ + UsbDevice device = null; + UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); + /*for(UsbDevice v : usbManager.getDeviceList().values()) + if(v.getDeviceId() == deviceId) + device = v;*/ + Collection devices = usbManager.getDeviceList().values(); + if (devices.size() == 0) + return; + device = devices.iterator().next(); + if(device == null) { + status(view,"connection failed: device not found"); + return; + } + UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if(driver == null) { + //status(view,"connection failed: no driver for device"); + //status(view, String.valueOf(device.getVendorId())); // 1155 = 0x483 ok + status(view, String.valueOf(device.getProductId())); + return; + } + if(driver.getPorts().size() < portNum) { + status(view,"connection failed: not enough ports at device"); + return; + } + usbSerialPort = driver.getPorts().get(portNum); + UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); + if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { + usbPermission = UsbPermission.Requested; + int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; + PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), flags); + usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); + return; + } + if(usbConnection == null) { + if (!usbManager.hasPermission(driver.getDevice())) + status(view,"connection failed: permission denied"); + else + status(view,"connection failed: open failed"); + return; + } + + try { + usbSerialPort.open(usbConnection); + usbSerialPort.setParameters(57600, 8, 1, UsbSerialPort.PARITY_NONE); + //if(withIoManager) { + // usbIoManager = new SerialInputOutputManager(usbSerialPort, this); + // usbIoManager.start(); + //} + status(view,"connected"); + + connected = true; + } catch (Exception e) { + status(view,"connection failed: " + e.getMessage()); + disconnect(); + } + } + + private void disconnect() { + connected = false; + if(usbIoManager != null) { + usbIoManager.setListener(null); + usbIoManager.stop(); + } + usbIoManager = null; + try { + usbSerialPort.close(); + } catch (IOException ignored) {} + usbSerialPort = null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/IRFrame.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/IRFrame.java new file mode 100644 index 0000000..66218a3 --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/IRFrame.java @@ -0,0 +1,64 @@ +package org.furrtek.pricehax; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class IRFrame { + public long PLID = 0x00000000; + public byte protocol = (byte)0x84; + public int repeats = 1; + public int delay = 30; + public List payload; + + private static int[] CRCLUT = { 0, 4489, 8978, 12955, 17956, 22445, 25910, 29887, 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735, 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662, 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510, 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949, 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797, 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724, 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572, 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011, 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859, 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786, 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634, 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073, 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921, 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848, 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696, 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623, 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999, 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398, 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774, 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685, 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061, 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460, 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836, 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747, 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123, 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522, 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898, 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809, 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185, 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584, 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 }; + + public IRFrame(long PLID, byte protocol, List payload, int delay, int repeats) { + this.PLID = PLID; + this.protocol = protocol; + this.payload = payload; + this.delay = delay; + this.repeats = repeats; + } + static long getCRC(List data) { + long l = 33800L; + for (byte b : data) { + l = CRCLUT[((int)((l ^ b) & 0xFF))] ^ (l >> 8); + } + return l; + } + public List getRawData(Boolean PP16) { + List frame_data = new ArrayList(); + + frame_data.add((byte) protocol); + + frame_data.add((byte) (PLID & 255)); + frame_data.add((byte) (PLID >> 8)); + frame_data.add((byte) (PLID >> 16)); + frame_data.add((byte) (PLID >> 24)); + + frame_data.addAll(payload); + + long crc = getCRC(frame_data); + frame_data.add((byte) crc); + frame_data.add((byte) (crc >> 8)); + + if (PP16) { + // Special PP16 header + Byte[] header = {0x00, 0x00, 0x00, (byte)0x40}; + frame_data.addAll(Arrays.asList(header)); + } + + // Debug + String hex_str = ""; + for (byte b : frame_data) { + hex_str += String.format("%02X ", b); + } + + Log.d("getRawData", hex_str); + + return frame_data; + } +} diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/MainActivity.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/MainActivity.java new file mode 100644 index 0000000..3b7060f --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/MainActivity.java @@ -0,0 +1,605 @@ +package org.furrtek.pricehax; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.hardware.Camera; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.AsyncTask; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialPort; +import com.hoho.android.usbserial.driver.UsbSerialProber; +import com.hoho.android.usbserial.util.SerialInputOutputManager; +import java.io.IOException; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.widget.*; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import com.google.android.material.snackbar.Snackbar; +import androidx.appcompat.app.AppCompatActivity; +import android.view.View; +import androidx.navigation.ui.AppBarConfiguration; + +import android.view.Menu; +import android.view.MenuItem; + +import net.sourceforge.zbar.Config; +import net.sourceforge.zbar.Image; +import net.sourceforge.zbar.ImageScanner; +import net.sourceforge.zbar.Symbol; +import org.furrtek.pricehax.databinding.ActivityMainBinding; + +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static android.provider.Settings.System.getString; +import static androidx.core.app.ActivityCompat.finishAffinity; + +public class MainActivity extends AppCompatActivity { + + private ActivityMainBinding binding; + private Handler autoFocusHandler; + private CameraPreview mPreview; + private long lastPLID = 0; + FrameLayout preview; + ImageScanner scanner; + String lastBarcodeString = ""; + int imageScale = 100; + DMImage dmImage = null; + boolean inBWR = false; + + private enum UsbPermission { Unknown, Requested, Granted, Denied } + private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; + + private int deviceId, portNum, baudRate; + private UsbSerialPort usbSerialPort; + private UsbPermission usbPermission = UsbPermission.Unknown; + private SerialInputOutputManager usbIoManager; + private boolean blasterConnected = false; + char blasterHWVersion; + int blasterFWVersion; + CoordinatorLayout mainlayout; + Uri selectedImageUri = null; + int dispDurationIdx, dispPage; + //AsyncResponseTX asyncTaskTX = null; + boolean transmitting = false; + boolean loopTX = false; + boolean autoTX = false; + + static { + System.loadLibrary("iconv"); + } + + @Override + public void onResume() { + super.onResume(); + testConnect(); + } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + setSupportActionBar(binding.toolbar); + + mainlayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout); + + // App e-stop + binding.main.fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finishAffinity(); + System.exit(0); + } + }); + + // Stop TX + binding.main.buttonTXStop.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (transmitting) { + byte[] data = {(byte)'S'}; + try { + usbSerialPort.write(data, 1000); + } catch(IOException ignore) { + } + binding.main.switchLoopTX.setChecked(false); + } + } + }); + + // Populate duration spinner + Spinner spinner = binding.main.spinnerDuration; + List arrayList = Arrays.asList("2s", "15s", "15m", "Forever"); + ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, arrayList); + arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(arrayAdapter); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + dispDurationIdx = position; + } + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + // Populate page spinner + List pageList = new ArrayList(); + for (int i = 0; i < 7; i++) + pageList.add(Integer.toString(i)); + ArrayAdapter arrayAdapterPage = new ArrayAdapter(this, android.R.layout.simple_spinner_item, pageList); + arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + binding.main.spinnerPage.setAdapter(arrayAdapterPage); + binding.main.spinnerPage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + dispPage = Integer.parseInt(parent.getItemAtPosition(position).toString()); + } + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + binding.main.buttonTXPageDM.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // 0x85, 0x00, 0x00, 0x00, 0x00, 0x06, 0xF1, 0x00, 0x00, 0x00, 0x0A, 0x5D, 0x14 + List frames = new ArrayList(); + Byte[] payload = {0x06, 0x00, 0x00, 0x00, 0x00, 0x00}; + payload[1] = (byte)(((dispPage & 7) << 3) | 1); + + if (dispDurationIdx != 3) { + int[] durations = {2, 15, 15*60, -1}; + int duration = durations[dispDurationIdx]; + payload[4] = (byte)(duration >> 8); + payload[5] = (byte)(duration & 255); + } else { + payload[1] = (byte)(payload[1] | 0x80); // "Forever" flag + } + + frames.add(new IRFrame(0, (byte)0x85, Arrays.asList(payload), 30, 200)); + IRTransmit(frames); + } + }); + + binding.main.buttonTXPage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // 0x84, 0x00, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00 + + List frames = new ArrayList(); + Byte[] payload = {(byte)0xAB, 0x00, 0x00, 0x00}; + + int[] durations = {1, 3, 5, 0x80}; + int duration = durations[dispDurationIdx]; + payload[1] = (byte)(((dispPage & 7) << 3) | duration); + + frames.add(new IRFrame(0, (byte)0x84, Arrays.asList(payload), 30, 200)); + IRTransmit(frames); + } + }); + + binding.main.switchLoopTX.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + loopTX = isChecked; + } + }); + + binding.main.switchTXAuto.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + autoTX = isChecked; + } + }); + + binding.main.buttonTXImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + TransmitImage(); + } + }); + + // Image selection and processing + ActivityResultLauncher someActivityResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + if (result.getData() != null) { + selectedImageUri = result.getData().getData(); + convertImage(); + } + } + } + }); + + binding.main.buttonLoadImg.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + photoPickerIntent.setType("image/*"); + photoPickerIntent.setAction(Intent.ACTION_GET_CONTENT); + someActivityResultLauncher.launch(photoPickerIntent); + } + }); + + // BW or BWR mode selection + binding.main.radiogroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + inBWR = binding.main.radioBWR.isChecked(); + updateImage(); + } + }); + + // Image scaling change + binding.main.seekScale.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { + } + @Override + public void onStartTrackingTouch(SeekBar seekbar) { + } + @Override + public void onStopTrackingTouch(SeekBar seekbar) { + imageScale = seekbar.getProgress(); + convertImage(); + } + }); + + // Start camera preview. Ask for camera permission if not already granted. + if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED){ + ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA}, 123); + } else { + setScanPreview(); + } + + // Register USB action receiver + BroadcastReceiver USBReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + setDisconnected(); + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { + testConnect(); + } + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + registerReceiver(USBReceiver, filter); + + // Load default image + selectedImageUri = Uri.parse("android.resource://org.furrtek.pricehax/" + R.drawable.dm_128x64); + convertImage(); + } + + private void TransmitImage() { + List frames = DMGen.DMGenFrames(dmImage, inBWR, lastPLID, dispPage); + IRTransmit(frames); + } + + private void convertImage() { + if (selectedImageUri == null) return; + try { + Bitmap selectedImage = BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImageUri)); + selectedImage = scaleImage(selectedImage); + + new DMConvert( + binding.main.progressbar, + new AsyncResponse() { + @Override + public void processFinish(DMImage dmimage) { + dmImage = dmimage; + binding.main.textScale.setText(getString(R.string.image_dimensions, dmimage.bitmapBW.getWidth(), dmimage.bitmapBW.getHeight())); + updateImage(); + Log.d("PHX", "Converted image ok"); + } + }).execute(selectedImage); + } catch (Exception e) { + Log.d("PHX", e.getLocalizedMessage()); + } + } + public void updateImage() { + if (dmImage != null) + binding.main.imageviewDm.setImageBitmap(inBWR ? dmImage.bitmapBWR : dmImage.bitmapBW); + } + + private Bitmap scaleImage(Bitmap selectedImage) { + float originalWidth = selectedImage.getWidth(); + float originalHeight = selectedImage.getHeight(); + int newWidth, newHeight; + if (originalWidth < 300) { + //imageScale = (int)(originalWidth * 100) / 300; + newWidth = (int)originalWidth; + newHeight = (int)originalHeight; + } else { + newWidth = (300 * imageScale) / 100; + newHeight = (int)(newWidth * (originalHeight / originalWidth)); + } + return Bitmap.createScaledBitmap(selectedImage, newWidth, newHeight, true); + } + + public void IRTransmit(List frames) { + if (!blasterConnected) return; + enableTXWidgets(false); + transmitting = true; + new ESLBlaster( + usbSerialPort, + binding.main.progressbar, + binding.main.textStatus2, + new AsyncResponseTX() { + @Override + public void processFinish(boolean result) { + if (loopTX) { + IRTransmit(frames); + } else { + transmitting = false; + enableTXWidgets(true); + } + } + }).execute(frames); + } + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == 123) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "Camera permission granted", Toast.LENGTH_LONG).show(); + setScanPreview(); + } else { + Toast.makeText(this, "Camera permission denied", Toast.LENGTH_LONG).show(); + // TODO: Close app ? + } + } + } + + Camera.PreviewCallback previewCb = new Camera.PreviewCallback() { + public void onPreviewFrame(byte[] data, Camera camera) { + String strSupplement; + Camera.Size size = camera.getParameters().getPreviewSize(); + Image previewimage = new Image(size.width, size.height, "Y800"); // FourCC for monochrome image + previewimage.setData(data); + if (MainActivity.this.scanner.scanImage(previewimage) != 0) { + Iterator it = MainActivity.this.scanner.getResults().iterator(); + //while (it.hasNext()) { + String barcodeString = ((Symbol) it.next()).getData(); + if (barcodeString != lastBarcodeString) { + if (barcodeValid(barcodeString)) { + lastPLID = (Integer.parseInt(barcodeString.substring(2, 7))<<16) + Integer.parseInt(barcodeString.substring(7, 12)); + String PLSerial = Long.toHexString(lastPLID); + while (PLSerial.length() < 8) + PLSerial = "0" + PLSerial; + if (PLSerial.length() > 8) + PLSerial = PLSerial.substring(8); + //lastPLType = Integer.parseInt(barcodeString.substring(12, 16)); + strSupplement = "OK " + PLSerial.toUpperCase(); + if (autoTX && !transmitting) + TransmitImage(); + } else { + strSupplement = "INVALID"; + lastPLID = 0; + } + binding.main.textLastScanned.setText("Last scanned:\n" + barcodeString + "\n (" + strSupplement + ")"); + } + lastBarcodeString = barcodeString; + + /*MainActivity.this.previewing = false; + camera.setPreviewCallback(null); + camera.stopPreview(); + MainActivity.this.scanButton.setText("Scan another ESL barcode?");*/ + //} + } + } + }; + private Runnable doAutoFocus = new Runnable() { + public void run() { + /*if (MainActivity.this.mCamera != null) { + MainActivity.this.mCamera.autoFocus(MainActivity.this.autoFocusCB); + }*/ + } + }; + + Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() { + public void onAutoFocus(boolean success, Camera camera) { + // postDelayed() will call doAutoFocus() and onAutoFocus() will be called again from mCamera.autoFocus(). + // This will create a timer that will readjust the focus every second. + MainActivity.this.autoFocusHandler.postDelayed(MainActivity.this.doAutoFocus, 1000); + } + }; + + public void setScanPreview() { + // Init Camera preview + this.autoFocusHandler = new Handler(); + this.mPreview = new CameraPreview(this, this.previewCb, this.autoFocusCB); + //this.preview = (FrameLayout) findViewById(R.id.cameraPreview); + this.preview = binding.main.cameraPreview; + this.preview.addView(this.mPreview); + + // Init ZBar + this.scanner = new ImageScanner(); + this.scanner.setConfig(0, Config.X_DENSITY, 3); + this.scanner.setConfig(0, Config.Y_DENSITY, 3); + this.scanner.setConfig(0, Config.MIN_LEN, 17); + this.scanner.setConfig(0, Config.MAX_LEN, 17); + // Disable all symbols except Code128 + this.scanner.setConfig(0, Config.ENABLE, 0); + this.scanner.setConfig(Symbol.CODE128, Config.ENABLE, 1); + } + boolean barcodeValid(String barcodeString) { + if (barcodeString.charAt(1) != '4') return false; + if (Integer.parseInt(barcodeString.substring(5, 6)) > 53) return false; + int sum = 0; + for (int i = 0; i < barcodeString.length() - 1; i++) + sum += (int)barcodeString.charAt(i); + if (sum % 10 != Integer.parseInt(barcodeString.substring(16, 17))) return false; + return true; + } + + public void status(String msg) { + Snackbar.make(mainlayout, msg, Snackbar.LENGTH_LONG).setAction("Action", null).show(); + } + + public void testConnect() { + UsbDevice device = null; + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + /*for(UsbDevice v : usbManager.getDeviceList().values()) + if(v.getDeviceId() == deviceId) + device = v;*/ + Collection devices = usbManager.getDeviceList().values(); + if (devices.size() == 0) + return; + device = devices.iterator().next(); + if (device == null) { + status("ESL Blaster connection failed: device not found"); + return; + } + UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if (driver == null) { + //status(view,"connection failed: no driver for device"); + //status(view, String.valueOf(device.getVendorId())); // 1155 = 0x483 ok + status(String.valueOf(device.getProductId())); + return; + } + if (driver.getPorts().size() < portNum) { + status("ESL Blaster connection failed: not enough ports at device"); + return; + } + usbSerialPort = driver.getPorts().get(portNum); + UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); + if (usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { + usbPermission = UsbPermission.Requested; + int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; + PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(INTENT_ACTION_GRANT_USB), flags); + usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); + return; + } + if (usbConnection == null) { + if (!usbManager.hasPermission(driver.getDevice())) + status("ESL Blaster connection failed: permission denied"); + else + status("ESL Blaster connection failed: open failed"); + return; + } + + try { + usbSerialPort.open(usbConnection); + usbSerialPort.setParameters(57600, 8, 1, UsbSerialPort.PARITY_NONE); + + try { + byte[] buffer = new byte[256]; + byte[] data = {(byte)'?'}; + usbSerialPort.write(data, 1000); + if (usbSerialPort.read(buffer, 2000) > 0) { // 2s timeout should be enough + String s = new String(buffer, StandardCharsets.UTF_8).substring(0, 12); + Log.d("PHX", String.valueOf((char)buffer[0])); + if (s.substring(0, 10).equals("ESLBlaster")) { + status("ESL Blaster connected !"); + blasterHWVersion = s.charAt(10); + blasterFWVersion = Character.digit(s.charAt(11), 10); + blasterConnected = true; + enableTXWidgets(true); + binding.main.textStatus.setText(String.format("ESLBlaster connected (HW %c, FW %d)", blasterHWVersion, blasterFWVersion)); + } + }; + } catch(IOException e) { + e.printStackTrace(); + setDisconnected(); + } + + } catch (Exception e) { + status("ESL Blaster connection failed: " + e.getMessage()); + setDisconnected(); + } + } + + public void enableTXWidgets(boolean enable) { + binding.main.buttonTXPage.setEnabled(enable); + binding.main.buttonTXPageDM.setEnabled(enable); + binding.main.buttonTXImage.setEnabled(enable); + } + + private void setDisconnected() { + blasterConnected = false; + enableTXWidgets(false); + status("ESL Blaster disconnected !"); + binding.main.textStatus.setText("ESL Blaster not connected"); + if (usbIoManager != null) { + usbIoManager.setListener(null); + usbIoManager.stop(); + usbIoManager = null; + } + try { + usbSerialPort.close(); + } catch (IOException ignored) {} + usbSerialPort = null; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_about) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("App and ESL Blaster by furrtek\nhttps://github.com/furrtek/PrecIR/\n\nThanks to: Aoi, david4599, Deadbird, Dr.Windaube, Sigmounte, BiduleOhm, Virtualabs, LightSnivy") + .setTitle("About"); + AlertDialog dialog = builder.create(); + dialog.show(); + return true; + } else if (id == R.id.action_help) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("Use an ESL Blaster with a USB OTG cable to use this app. Phone IR transmitters aren't fast enough to communicate with ESLs.\n\nPage change doesn't require a valid barcode (found printed on front or back of ESL) to be scanned. Changing segments or images do.\n\nDM ESL images won't update if the image is too big or in the wrong mode. A B/W 50x50px image should always work.") + .setTitle("Help"); + AlertDialog dialog = builder.create(); + dialog.show(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /*@Override + protected void onNewIntent(Intent intent) { + if("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) { + //Log.i("PriceHax", "Device detected !"); + //MainActivity.this.scaneibarcode.setText("USB_DEVICE_ATTACHED !"); + } + super.onNewIntent(intent); + }*/ +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/SecondFragment.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/SecondFragment.java new file mode 100644 index 0000000..8a010cf --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/SecondFragment.java @@ -0,0 +1,46 @@ +package org.furrtek.pricehax; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.navigation.fragment.NavHostFragment; +import org.furrtek.pricehax.databinding.FragmentSecondBinding; + +public class SecondFragment extends Fragment { + + private FragmentSecondBinding binding; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState + ) { + + binding = FragmentSecondBinding.inflate(inflater, container, false); + return binding.getRoot(); + + } + + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + binding.buttonSecond.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + NavHostFragment.findNavController(SecondFragment.this) + .navigate(R.id.action_SecondFragment_to_FirstFragment); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + +} \ No newline at end of file diff --git a/PriceHax/app/src/main/java/org/furrtek/pricehax/TXProgress.java b/PriceHax/app/src/main/java/org/furrtek/pricehax/TXProgress.java new file mode 100644 index 0000000..20d6bdb --- /dev/null +++ b/PriceHax/app/src/main/java/org/furrtek/pricehax/TXProgress.java @@ -0,0 +1,10 @@ +package org.furrtek.pricehax; + +public class TXProgress { + public int percent; + public String text; + public TXProgress(int percent, String text) { + this.percent = percent; + this.text = text; + } +} diff --git a/PriceHax/app/src/main/jnilibs/arm64-v8a/libiconv.so b/PriceHax/app/src/main/jnilibs/arm64-v8a/libiconv.so new file mode 100644 index 0000000..31c8e4f Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/arm64-v8a/libiconv.so differ diff --git a/PriceHax/app/src/main/jnilibs/arm64-v8a/libzbarjni.so b/PriceHax/app/src/main/jnilibs/arm64-v8a/libzbarjni.so new file mode 100644 index 0000000..9e0ce49 Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/arm64-v8a/libzbarjni.so differ diff --git a/PriceHax/app/src/main/jnilibs/armeabi-v7a/libiconv.so b/PriceHax/app/src/main/jnilibs/armeabi-v7a/libiconv.so new file mode 100644 index 0000000..2bcbb70 Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/armeabi-v7a/libiconv.so differ diff --git a/PriceHax/app/src/main/jnilibs/armeabi-v7a/libzbarjni.so b/PriceHax/app/src/main/jnilibs/armeabi-v7a/libzbarjni.so new file mode 100644 index 0000000..2693dbb Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/armeabi-v7a/libzbarjni.so differ diff --git a/PriceHax/app/src/main/jnilibs/armeabi/libiconv.so b/PriceHax/app/src/main/jnilibs/armeabi/libiconv.so new file mode 100644 index 0000000..9c7150d Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/armeabi/libiconv.so differ diff --git a/PriceHax/app/src/main/jnilibs/armeabi/libzbarjni.so b/PriceHax/app/src/main/jnilibs/armeabi/libzbarjni.so new file mode 100644 index 0000000..3d4a8ac Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/armeabi/libzbarjni.so differ diff --git a/PriceHax/app/src/main/jnilibs/x86/libiconv.so b/PriceHax/app/src/main/jnilibs/x86/libiconv.so new file mode 100644 index 0000000..6ab43e5 Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/x86/libiconv.so differ diff --git a/PriceHax/app/src/main/jnilibs/x86/libzbarjni.so b/PriceHax/app/src/main/jnilibs/x86/libzbarjni.so new file mode 100644 index 0000000..d64f517 Binary files /dev/null and b/PriceHax/app/src/main/jnilibs/x86/libzbarjni.so differ diff --git a/PriceHax/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/PriceHax/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1ee1493 --- /dev/null +++ b/PriceHax/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/PriceHax/app/src/main/res/drawable/dm_128x64.png b/PriceHax/app/src/main/res/drawable/dm_128x64.png new file mode 100644 index 0000000..7de5835 Binary files /dev/null and b/PriceHax/app/src/main/res/drawable/dm_128x64.png differ diff --git a/PriceHax/app/src/main/res/drawable/ic_launcher_background.xml b/PriceHax/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..956b344 --- /dev/null +++ b/PriceHax/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PriceHax/app/src/main/res/drawable/orangeframe.xml b/PriceHax/app/src/main/res/drawable/orangeframe.xml new file mode 100644 index 0000000..6c2610f --- /dev/null +++ b/PriceHax/app/src/main/res/drawable/orangeframe.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/PriceHax/app/src/main/res/layout/activity_main.xml b/PriceHax/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5e60234 --- /dev/null +++ b/PriceHax/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/PriceHax/app/src/main/res/layout/content_main.xml b/PriceHax/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..0fffb87 --- /dev/null +++ b/PriceHax/app/src/main/res/layout/content_main.xml @@ -0,0 +1,194 @@ + + +