cordova-plugin-mlkit-suite 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +526 -640
- package/docs/screenshots/ios-text-recognition.jpg +0 -0
- package/package.json +7 -3
- package/plugin.xml +149 -134
- package/src/android/java/BarcodeFrameAnalyzer.java +115 -111
- package/src/android/java/BarcodeOverlayView.java +83 -25
- package/src/android/java/BarcodeScanActivity.java +100 -12
- package/src/android/java/BarcodeScanConfig.java +6 -1
- package/src/android/java/BarcodeScanRegion.java +26 -8
- package/src/android/java/MlKitModelsModuleInstaller.java +35 -16
- package/src/android/java/MlKitSuitePlugin.java +315 -33
- package/src/android/java/SelfieSegmentationProcessor.java +215 -0
- package/src/android/java/StaticImageProcessor.java +208 -151
- package/src/android/java/SubjectSegmentationModuleInstaller.java +154 -0
- package/src/android/java/SubjectSegmentationProcessor.java +189 -0
- package/src/android/mlkit-suite.gradle +30 -0
- package/src/android/res/values/mlkit_suite_strings.xml +8 -0
- package/src/android/res/values-es/mlkit_suite_strings.xml +8 -0
- package/src/ios/BarcodeOverlayView.swift +57 -6
- package/src/ios/BarcodeScanTypes.swift +3 -1
- package/src/ios/BarcodeScanViewController.swift +120 -5
- package/src/ios/MlKitSuitePlugin.swift +222 -13
- package/src/ios/MlKitSuiteResources.bundle/en.lproj/Localizable.strings +4 -0
- package/src/ios/MlKitSuiteResources.bundle/es.lproj/Localizable.strings +4 -0
- package/src/ios/SelfieSegmentationProcessor.swift +105 -0
- package/src/ios/TextRecognitionProcessor.swift +99 -0
- package/tests/ios-image-labeling.test.js +17 -0
- package/tests/ios-live-barcode.test.js +5 -0
- package/tests/ios-selfie-segmentation.test.js +24 -0
- package/tests/ios-static-text.test.js +21 -0
- package/tests/java/BarcodeScanRegionTest.java +12 -0
- package/tests/java-confirmation.test.js +41 -41
- package/tests/js-api.test.js +42 -2
- package/www/MlKit.js +111 -7
- package/docs/screenshots/capabilities.jpg +0 -0
- package/docs/screenshots/demo-tour.gif +0 -0
- package/docs/screenshots/text-recognition.jpg +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.andreszs.plugins.mlkitsuite;
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator;
|
|
3
4
|
import android.content.Context;
|
|
4
5
|
import android.graphics.Canvas;
|
|
5
6
|
import android.graphics.Color;
|
|
@@ -9,14 +10,20 @@ import android.graphics.Rect;
|
|
|
9
10
|
import android.graphics.RectF;
|
|
10
11
|
import android.util.AttributeSet;
|
|
11
12
|
import android.view.View;
|
|
13
|
+
import android.view.animation.DecelerateInterpolator;
|
|
12
14
|
|
|
13
15
|
final class BarcodeOverlayView extends View {
|
|
14
16
|
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
15
17
|
private final RectF guide = new RectF();
|
|
18
|
+
private final RectF animatedGuide = new RectF();
|
|
19
|
+
private final RectF animationStart = new RectF();
|
|
20
|
+
private final RectF animationTarget = new RectF();
|
|
16
21
|
private final String instruction;
|
|
17
22
|
private final Integer guideColor;
|
|
18
23
|
private String message;
|
|
19
24
|
private boolean candidateVisible;
|
|
25
|
+
private boolean successVisible;
|
|
26
|
+
private ValueAnimator successAnimator;
|
|
20
27
|
|
|
21
28
|
BarcodeOverlayView(Context context, String instruction, Integer guideColor,
|
|
22
29
|
String initialMessage) {
|
|
@@ -49,35 +56,76 @@ final class BarcodeOverlayView extends View {
|
|
|
49
56
|
);
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
void showSuccess(Rect bounds, int imageWidth, int imageHeight, String text) {
|
|
60
|
+
updateGuide(getWidth(), getHeight());
|
|
61
|
+
float[] mapped = bounds == null ? null : BarcodeScanRegion.map(
|
|
62
|
+
getWidth(), getHeight(), imageWidth, imageHeight,
|
|
63
|
+
bounds.left, bounds.top, bounds.right, bounds.bottom
|
|
64
|
+
);
|
|
65
|
+
animationStart.set(guide);
|
|
66
|
+
if (mapped == null) {
|
|
67
|
+
animationTarget.set(guide);
|
|
68
|
+
} else {
|
|
69
|
+
float padding = dp(10);
|
|
70
|
+
animationTarget.set(
|
|
71
|
+
Math.max(0, mapped[0] - padding),
|
|
72
|
+
Math.max(0, mapped[1] - padding),
|
|
73
|
+
Math.min(getWidth(), mapped[2] + padding),
|
|
74
|
+
Math.min(getHeight(), mapped[3] + padding)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
animatedGuide.set(animationStart);
|
|
78
|
+
message = text;
|
|
79
|
+
candidateVisible = true;
|
|
80
|
+
successVisible = true;
|
|
81
|
+
if (successAnimator != null) successAnimator.cancel();
|
|
82
|
+
successAnimator = ValueAnimator.ofFloat(0f, 1f);
|
|
83
|
+
successAnimator.setDuration(220L);
|
|
84
|
+
successAnimator.setInterpolator(new DecelerateInterpolator());
|
|
85
|
+
successAnimator.addUpdateListener(animation -> {
|
|
86
|
+
float progress = (float) animation.getAnimatedValue();
|
|
87
|
+
animatedGuide.set(
|
|
88
|
+
lerp(animationStart.left, animationTarget.left, progress),
|
|
89
|
+
lerp(animationStart.top, animationTarget.top, progress),
|
|
90
|
+
lerp(animationStart.right, animationTarget.right, progress),
|
|
91
|
+
lerp(animationStart.bottom, animationTarget.bottom, progress)
|
|
92
|
+
);
|
|
93
|
+
invalidate();
|
|
94
|
+
});
|
|
95
|
+
successAnimator.start();
|
|
96
|
+
}
|
|
97
|
+
|
|
52
98
|
@Override
|
|
53
99
|
protected void onDraw(Canvas canvas) {
|
|
54
100
|
super.onDraw(canvas);
|
|
55
101
|
float width = getWidth();
|
|
56
102
|
float height = getHeight();
|
|
57
103
|
updateGuide(width, height);
|
|
104
|
+
RectF frame = successVisible ? animatedGuide : guide;
|
|
58
105
|
|
|
59
106
|
Path shade = new Path();
|
|
60
107
|
shade.setFillType(Path.FillType.EVEN_ODD);
|
|
61
108
|
shade.addRect(0, 0, width, height, Path.Direction.CW);
|
|
62
|
-
shade.addRoundRect(
|
|
109
|
+
shade.addRoundRect(frame, dp(18), dp(18), Path.Direction.CCW);
|
|
63
110
|
paint.setStyle(Paint.Style.FILL);
|
|
64
111
|
paint.setColor(0x99000000);
|
|
65
112
|
canvas.drawPath(shade, paint);
|
|
66
113
|
|
|
67
114
|
int color = candidateVisible ? 0xff4ade80 : Color.WHITE;
|
|
68
115
|
if (candidateVisible) {
|
|
69
|
-
drawCornerFrame(canvas, color, color, color, color);
|
|
116
|
+
drawCornerFrame(canvas, frame, color, color, color, color);
|
|
70
117
|
} else if (guideColor != null) {
|
|
71
|
-
drawCornerFrame(canvas, guideColor, guideColor, guideColor, guideColor);
|
|
118
|
+
drawCornerFrame(canvas, frame, guideColor, guideColor, guideColor, guideColor);
|
|
72
119
|
} else {
|
|
73
|
-
drawCornerFrame(canvas, 0xff4285f4, 0xfffbbc04, 0xff34a853,
|
|
120
|
+
drawCornerFrame(canvas, frame, 0xff4285f4, 0xfffbbc04, 0xff34a853,
|
|
121
|
+
0xffea4335);
|
|
74
122
|
}
|
|
75
123
|
|
|
76
|
-
if (!instruction.isEmpty()) {
|
|
124
|
+
if (!successVisible && !instruction.isEmpty()) {
|
|
77
125
|
drawCenteredText(canvas, instruction, Math.max(dp(72), guide.top - dp(42)),
|
|
78
126
|
dp(17), Color.WHITE);
|
|
79
127
|
}
|
|
80
|
-
drawCenteredText(canvas, message, Math.min(height - dp(72),
|
|
128
|
+
drawCenteredText(canvas, message, Math.min(height - dp(72), frame.bottom + dp(54)),
|
|
81
129
|
dp(17), color);
|
|
82
130
|
}
|
|
83
131
|
|
|
@@ -88,44 +136,44 @@ final class BarcodeOverlayView extends View {
|
|
|
88
136
|
width / 2f + guideSize / 2f, centerY + guideSize / 2f);
|
|
89
137
|
}
|
|
90
138
|
|
|
91
|
-
private void drawCornerFrame(Canvas canvas, int topLeft, int topRight,
|
|
139
|
+
private void drawCornerFrame(Canvas canvas, RectF frame, int topLeft, int topRight,
|
|
92
140
|
int bottomLeft, int bottomRight) {
|
|
93
|
-
float radius = dp(22);
|
|
94
|
-
float leg = Math.min(
|
|
141
|
+
float radius = Math.min(dp(22), Math.min(frame.width(), frame.height()) * 0.25f);
|
|
142
|
+
float leg = Math.min(Math.min(frame.width(), frame.height()) * 0.32f, dp(76));
|
|
95
143
|
paint.setStyle(Paint.Style.STROKE);
|
|
96
144
|
paint.setStrokeWidth(dp(4));
|
|
97
145
|
paint.setStrokeCap(Paint.Cap.ROUND);
|
|
98
146
|
paint.setStrokeJoin(Paint.Join.ROUND);
|
|
99
147
|
|
|
100
148
|
Path path = new Path();
|
|
101
|
-
path.moveTo(
|
|
102
|
-
path.lineTo(
|
|
103
|
-
path.quadTo(
|
|
104
|
-
path.lineTo(
|
|
149
|
+
path.moveTo(frame.left, frame.top + leg);
|
|
150
|
+
path.lineTo(frame.left, frame.top + radius);
|
|
151
|
+
path.quadTo(frame.left, frame.top, frame.left + radius, frame.top);
|
|
152
|
+
path.lineTo(frame.left + leg, frame.top);
|
|
105
153
|
paint.setColor(topLeft);
|
|
106
154
|
canvas.drawPath(path, paint);
|
|
107
155
|
|
|
108
156
|
path.reset();
|
|
109
|
-
path.moveTo(
|
|
110
|
-
path.lineTo(
|
|
111
|
-
path.quadTo(
|
|
112
|
-
path.lineTo(
|
|
157
|
+
path.moveTo(frame.right - leg, frame.top);
|
|
158
|
+
path.lineTo(frame.right - radius, frame.top);
|
|
159
|
+
path.quadTo(frame.right, frame.top, frame.right, frame.top + radius);
|
|
160
|
+
path.lineTo(frame.right, frame.top + leg);
|
|
113
161
|
paint.setColor(topRight);
|
|
114
162
|
canvas.drawPath(path, paint);
|
|
115
163
|
|
|
116
164
|
path.reset();
|
|
117
|
-
path.moveTo(
|
|
118
|
-
path.lineTo(
|
|
119
|
-
path.quadTo(
|
|
120
|
-
path.lineTo(
|
|
165
|
+
path.moveTo(frame.left, frame.bottom - leg);
|
|
166
|
+
path.lineTo(frame.left, frame.bottom - radius);
|
|
167
|
+
path.quadTo(frame.left, frame.bottom, frame.left + radius, frame.bottom);
|
|
168
|
+
path.lineTo(frame.left + leg, frame.bottom);
|
|
121
169
|
paint.setColor(bottomLeft);
|
|
122
170
|
canvas.drawPath(path, paint);
|
|
123
171
|
|
|
124
172
|
path.reset();
|
|
125
|
-
path.moveTo(
|
|
126
|
-
path.lineTo(
|
|
127
|
-
path.quadTo(
|
|
128
|
-
path.lineTo(
|
|
173
|
+
path.moveTo(frame.right - leg, frame.bottom);
|
|
174
|
+
path.lineTo(frame.right - radius, frame.bottom);
|
|
175
|
+
path.quadTo(frame.right, frame.bottom, frame.right, frame.bottom - radius);
|
|
176
|
+
path.lineTo(frame.right, frame.bottom - leg);
|
|
129
177
|
paint.setColor(bottomRight);
|
|
130
178
|
canvas.drawPath(path, paint);
|
|
131
179
|
paint.setStrokeCap(Paint.Cap.BUTT);
|
|
@@ -147,4 +195,14 @@ final class BarcodeOverlayView extends View {
|
|
|
147
195
|
private float dp(float value) {
|
|
148
196
|
return value * getResources().getDisplayMetrics().density;
|
|
149
197
|
}
|
|
198
|
+
|
|
199
|
+
private float lerp(float start, float end, float progress) {
|
|
200
|
+
return start + (end - start) * progress;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Override
|
|
204
|
+
protected void onDetachedFromWindow() {
|
|
205
|
+
if (successAnimator != null) successAnimator.cancel();
|
|
206
|
+
super.onDetachedFromWindow();
|
|
207
|
+
}
|
|
150
208
|
}
|
|
@@ -2,7 +2,13 @@ package com.andreszs.plugins.mlkitsuite;
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
4
|
import android.content.Intent;
|
|
5
|
+
import android.graphics.Bitmap;
|
|
5
6
|
import android.graphics.Color;
|
|
7
|
+
import android.graphics.Rect;
|
|
8
|
+
import android.media.AudioAttributes;
|
|
9
|
+
import android.media.AudioFormat;
|
|
10
|
+
import android.media.AudioManager;
|
|
11
|
+
import android.media.AudioTrack;
|
|
6
12
|
import android.os.Bundle;
|
|
7
13
|
import android.os.Handler;
|
|
8
14
|
import android.os.Looper;
|
|
@@ -13,6 +19,7 @@ import android.view.ViewGroup;
|
|
|
13
19
|
import android.view.WindowManager;
|
|
14
20
|
import android.widget.FrameLayout;
|
|
15
21
|
import android.widget.ImageButton;
|
|
22
|
+
import android.widget.ImageView;
|
|
16
23
|
|
|
17
24
|
import androidx.annotation.NonNull;
|
|
18
25
|
import androidx.annotation.Nullable;
|
|
@@ -38,6 +45,8 @@ import java.util.concurrent.Executors;
|
|
|
38
45
|
public final class BarcodeScanActivity extends Activity
|
|
39
46
|
implements LifecycleOwner, BarcodeFrameAnalyzer.Listener {
|
|
40
47
|
|
|
48
|
+
private static final long SUCCESS_FEEDBACK_MS = 450L;
|
|
49
|
+
|
|
41
50
|
public static final String EXTRA_OPTIONS_JSON = "mlKitSuite.barcode.options";
|
|
42
51
|
public static final String EXTRA_RESULT_JSON = "mlKitSuite.barcode.result";
|
|
43
52
|
public static final String EXTRA_ERROR_CODE = "mlKitSuite.barcode.errorCode";
|
|
@@ -48,13 +57,16 @@ public final class BarcodeScanActivity extends Activity
|
|
|
48
57
|
private final ExecutorService analysisExecutor = Executors.newSingleThreadExecutor();
|
|
49
58
|
private LifecycleRegistry lifecycleRegistry;
|
|
50
59
|
private BarcodeScanConfig config;
|
|
60
|
+
private FrameLayout rootView;
|
|
51
61
|
private PreviewView previewView;
|
|
52
62
|
private BarcodeOverlayView overlayView;
|
|
63
|
+
private ImageButton closeButton;
|
|
53
64
|
private ImageButton torchButton;
|
|
54
65
|
private ProcessCameraProvider cameraProvider;
|
|
55
66
|
private ImageAnalysis imageAnalysis;
|
|
56
67
|
private BarcodeFrameAnalyzer analyzer;
|
|
57
68
|
private Camera camera;
|
|
69
|
+
private AudioTrack successBeep;
|
|
58
70
|
private boolean torchEnabled;
|
|
59
71
|
private boolean completed;
|
|
60
72
|
private Runnable timeoutRunnable;
|
|
@@ -112,6 +124,7 @@ public final class BarcodeScanActivity extends Activity
|
|
|
112
124
|
if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
|
|
113
125
|
if (analyzer != null) analyzer.close();
|
|
114
126
|
if (cameraProvider != null) cameraProvider.unbindAll();
|
|
127
|
+
releaseSuccessBeep();
|
|
115
128
|
analysisExecutor.shutdownNow();
|
|
116
129
|
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
|
|
117
130
|
super.onDestroy();
|
|
@@ -123,27 +136,27 @@ public final class BarcodeScanActivity extends Activity
|
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
private void createInterface() {
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
rootView = new FrameLayout(this);
|
|
140
|
+
rootView.setBackgroundColor(Color.BLACK);
|
|
128
141
|
previewView = new PreviewView(this);
|
|
129
142
|
previewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
|
|
130
143
|
previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
|
|
131
|
-
|
|
144
|
+
rootView.addView(previewView, matchParent());
|
|
132
145
|
|
|
133
146
|
overlayView = new BarcodeOverlayView(this, config.instructionText, config.guideColor,
|
|
134
147
|
config.localization.text("barcode_searching"));
|
|
135
|
-
|
|
148
|
+
rootView.addView(overlayView, matchParent());
|
|
136
149
|
|
|
137
150
|
if (config.showCloseButton) {
|
|
138
|
-
|
|
151
|
+
closeButton = cameraIconButton(
|
|
139
152
|
BarcodeIconDrawable.CLOSE, config.localization.text("close_barcode"), false
|
|
140
153
|
);
|
|
141
|
-
|
|
154
|
+
closeButton.setOnClickListener(view -> cancelByUser());
|
|
142
155
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(dp(52), dp(52));
|
|
143
156
|
params.gravity = Gravity.TOP | Gravity.START;
|
|
144
157
|
params.topMargin = dp(12);
|
|
145
158
|
params.leftMargin = dp(12);
|
|
146
|
-
|
|
159
|
+
rootView.addView(closeButton, params);
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
if (config.showTorchButton) {
|
|
@@ -156,9 +169,9 @@ public final class BarcodeScanActivity extends Activity
|
|
|
156
169
|
params.gravity = Gravity.TOP | Gravity.END;
|
|
157
170
|
params.topMargin = dp(12);
|
|
158
171
|
params.rightMargin = dp(12);
|
|
159
|
-
|
|
172
|
+
rootView.addView(torchButton, params);
|
|
160
173
|
}
|
|
161
|
-
setContentView(
|
|
174
|
+
setContentView(rootView);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
private ImageButton cameraIconButton(int icon, String description, boolean active) {
|
|
@@ -246,9 +259,26 @@ public final class BarcodeScanActivity extends Activity
|
|
|
246
259
|
}
|
|
247
260
|
|
|
248
261
|
@Override
|
|
249
|
-
public void onConfirmed(String text, String format
|
|
262
|
+
public void onConfirmed(String text, String format, Rect bounds,
|
|
263
|
+
int imageWidth, int imageHeight) {
|
|
250
264
|
if (completed) return;
|
|
251
265
|
completed = true;
|
|
266
|
+
if (timeoutRunnable != null) mainHandler.removeCallbacks(timeoutRunnable);
|
|
267
|
+
if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
|
|
268
|
+
Bitmap frozenFrame = previewView.getBitmap();
|
|
269
|
+
if (frozenFrame != null) {
|
|
270
|
+
ImageView frozenView = new ImageView(this);
|
|
271
|
+
frozenView.setImageBitmap(frozenFrame);
|
|
272
|
+
frozenView.setScaleType(ImageView.ScaleType.FIT_XY);
|
|
273
|
+
rootView.addView(frozenView, 1, matchParent());
|
|
274
|
+
}
|
|
275
|
+
if (cameraProvider != null) cameraProvider.unbindAll();
|
|
276
|
+
if (closeButton != null) closeButton.setVisibility(View.GONE);
|
|
277
|
+
if (torchButton != null) torchButton.setVisibility(View.GONE);
|
|
278
|
+
overlayView.showSuccess(bounds, imageWidth, imageHeight,
|
|
279
|
+
config.localization.text("barcode_success"));
|
|
280
|
+
playSuccessBeep();
|
|
281
|
+
|
|
252
282
|
JSONObject result = new JSONObject();
|
|
253
283
|
try {
|
|
254
284
|
result.put("status", "success");
|
|
@@ -257,8 +287,10 @@ public final class BarcodeScanActivity extends Activity
|
|
|
257
287
|
} catch (Exception ignored) { }
|
|
258
288
|
Intent intent = new Intent();
|
|
259
289
|
intent.putExtra(EXTRA_RESULT_JSON, result.toString());
|
|
260
|
-
|
|
261
|
-
|
|
290
|
+
mainHandler.postDelayed(() -> {
|
|
291
|
+
setResult(RESULT_OK, intent);
|
|
292
|
+
finish();
|
|
293
|
+
}, SUCCESS_FEEDBACK_MS);
|
|
262
294
|
}
|
|
263
295
|
|
|
264
296
|
@Override
|
|
@@ -297,6 +329,62 @@ public final class BarcodeScanActivity extends Activity
|
|
|
297
329
|
mainHandler.postDelayed(timeoutRunnable, config.timeoutMs);
|
|
298
330
|
}
|
|
299
331
|
|
|
332
|
+
private void playSuccessBeep() {
|
|
333
|
+
if (!config.beepEnabled) return;
|
|
334
|
+
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
|
335
|
+
if (audioManager != null && audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
final int sampleRate = 22050;
|
|
339
|
+
final int sampleCount = sampleRate / 10;
|
|
340
|
+
byte[] pcm = new byte[sampleCount * 2];
|
|
341
|
+
for (int index = 0; index < sampleCount; index++) {
|
|
342
|
+
double time = index / (double) sampleRate;
|
|
343
|
+
double remaining = (sampleCount - index) / (double) sampleRate;
|
|
344
|
+
double envelope = Math.min(1d, time / 0.005d) * Math.min(1d, remaining / 0.025d);
|
|
345
|
+
short sample = (short) Math.round(Math.sin(2d * Math.PI * 1800d * time)
|
|
346
|
+
* envelope * Short.MAX_VALUE * 0.55d);
|
|
347
|
+
pcm[index * 2] = (byte) (sample & 0xff);
|
|
348
|
+
pcm[index * 2 + 1] = (byte) ((sample >>> 8) & 0xff);
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
AudioTrack track = new AudioTrack.Builder()
|
|
352
|
+
.setAudioAttributes(new AudioAttributes.Builder()
|
|
353
|
+
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
|
|
354
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
355
|
+
.build())
|
|
356
|
+
.setAudioFormat(new AudioFormat.Builder()
|
|
357
|
+
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
|
358
|
+
.setSampleRate(sampleRate)
|
|
359
|
+
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
|
|
360
|
+
.build())
|
|
361
|
+
.setTransferMode(AudioTrack.MODE_STATIC)
|
|
362
|
+
.setBufferSizeInBytes(pcm.length)
|
|
363
|
+
.build();
|
|
364
|
+
if (track.write(pcm, 0, pcm.length) <= 0) {
|
|
365
|
+
track.release();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
releaseSuccessBeep();
|
|
369
|
+
successBeep = track;
|
|
370
|
+
track.play();
|
|
371
|
+
mainHandler.postDelayed(() -> {
|
|
372
|
+
if (successBeep == track) releaseSuccessBeep();
|
|
373
|
+
}, 250L);
|
|
374
|
+
} catch (Exception ignored) {
|
|
375
|
+
releaseSuccessBeep();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private void releaseSuccessBeep() {
|
|
380
|
+
if (successBeep == null) return;
|
|
381
|
+
try {
|
|
382
|
+
successBeep.stop();
|
|
383
|
+
} catch (Exception ignored) { }
|
|
384
|
+
successBeep.release();
|
|
385
|
+
successBeep = null;
|
|
386
|
+
}
|
|
387
|
+
|
|
300
388
|
private FrameLayout.LayoutParams matchParent() {
|
|
301
389
|
return new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
|
302
390
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
@@ -43,6 +43,7 @@ final class BarcodeScanConfig {
|
|
|
43
43
|
final int[] formatValues;
|
|
44
44
|
final boolean showCloseButton;
|
|
45
45
|
final boolean showTorchButton;
|
|
46
|
+
final boolean beepEnabled;
|
|
46
47
|
final String instructionText;
|
|
47
48
|
final MlKitLocalization localization;
|
|
48
49
|
final Integer guideColor;
|
|
@@ -50,6 +51,7 @@ final class BarcodeScanConfig {
|
|
|
50
51
|
|
|
51
52
|
private BarcodeScanConfig(List<String> formatNames,
|
|
52
53
|
boolean showCloseButton, boolean showTorchButton,
|
|
54
|
+
boolean beepEnabled,
|
|
53
55
|
String instructionText, MlKitLocalization localization,
|
|
54
56
|
Integer guideColor, long timeoutMs) {
|
|
55
57
|
this.formatNames = Collections.unmodifiableList(formatNames);
|
|
@@ -59,6 +61,7 @@ final class BarcodeScanConfig {
|
|
|
59
61
|
}
|
|
60
62
|
this.showCloseButton = showCloseButton;
|
|
61
63
|
this.showTorchButton = showTorchButton;
|
|
64
|
+
this.beepEnabled = beepEnabled;
|
|
62
65
|
this.instructionText = instructionText;
|
|
63
66
|
this.localization = localization;
|
|
64
67
|
this.guideColor = guideColor;
|
|
@@ -72,11 +75,12 @@ final class BarcodeScanConfig {
|
|
|
72
75
|
List<String> formats = parseFormats(value.opt("formats"));
|
|
73
76
|
boolean close = strictBoolean(value, "showCloseButton", true);
|
|
74
77
|
boolean torch = strictBoolean(value, "showTorchButton", true);
|
|
78
|
+
boolean beep = strictBoolean(value, "beepEnabled", true);
|
|
75
79
|
String instruction = strictString(value, "instructionText",
|
|
76
80
|
localization.text("barcode_instruction"));
|
|
77
81
|
Integer guideColor = strictColor(value, "guideColor");
|
|
78
82
|
long timeout = strictLong(value, "timeoutMs", 0L, 0L, 600000L);
|
|
79
|
-
return new BarcodeScanConfig(formats, close, torch, instruction, localization,
|
|
83
|
+
return new BarcodeScanConfig(formats, close, torch, beep, instruction, localization,
|
|
80
84
|
guideColor, timeout);
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -146,6 +150,7 @@ final class BarcodeScanConfig {
|
|
|
146
150
|
String key = keys.next();
|
|
147
151
|
if (!("formats".equals(key)
|
|
148
152
|
|| "showCloseButton".equals(key) || "showTorchButton".equals(key)
|
|
153
|
+
|| "beepEnabled".equals(key)
|
|
149
154
|
|| "instructionText".equals(key) || "guideColor".equals(key)
|
|
150
155
|
|| "timeoutMs".equals(key)
|
|
151
156
|
|| "moduleLoadingMessage".equals(key) || "locale".equals(key))) {
|
|
@@ -17,17 +17,35 @@ final class BarcodeScanRegion {
|
|
|
17
17
|
float barcodeRight,
|
|
18
18
|
float barcodeBottom
|
|
19
19
|
) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
float[] mapped = map(viewWidth, viewHeight, imageWidth, imageHeight,
|
|
21
|
+
barcodeLeft, barcodeTop, barcodeRight, barcodeBottom);
|
|
22
|
+
return mapped != null
|
|
23
|
+
&& mapped[0] >= guideLeft && mapped[1] >= guideTop
|
|
24
|
+
&& mapped[2] <= guideRight && mapped[3] <= guideBottom;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static float[] map(
|
|
28
|
+
float viewWidth,
|
|
29
|
+
float viewHeight,
|
|
30
|
+
int imageWidth,
|
|
31
|
+
int imageHeight,
|
|
32
|
+
float barcodeLeft,
|
|
33
|
+
float barcodeTop,
|
|
34
|
+
float barcodeRight,
|
|
35
|
+
float barcodeBottom
|
|
36
|
+
) {
|
|
37
|
+
if (viewWidth <= 0 || viewHeight <= 0 || imageWidth <= 0 || imageHeight <= 0
|
|
38
|
+
|| barcodeRight <= barcodeLeft || barcodeBottom <= barcodeTop) {
|
|
39
|
+
return null;
|
|
22
40
|
}
|
|
23
41
|
float scale = Math.max(viewWidth / imageWidth, viewHeight / imageHeight);
|
|
24
42
|
float offsetX = (viewWidth - imageWidth * scale) / 2f;
|
|
25
43
|
float offsetY = (viewHeight - imageHeight * scale) / 2f;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
return new float[] {
|
|
45
|
+
barcodeLeft * scale + offsetX,
|
|
46
|
+
barcodeTop * scale + offsetY,
|
|
47
|
+
barcodeRight * scale + offsetX,
|
|
48
|
+
barcodeBottom * scale + offsetY
|
|
49
|
+
};
|
|
32
50
|
}
|
|
33
51
|
}
|
|
@@ -14,6 +14,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning;
|
|
|
14
14
|
import com.google.mlkit.vision.face.FaceDetection;
|
|
15
15
|
import com.google.mlkit.vision.face.FaceDetector;
|
|
16
16
|
import com.google.mlkit.vision.face.FaceDetectorOptions;
|
|
17
|
+
import com.google.mlkit.vision.label.ImageLabeler;
|
|
18
|
+
import com.google.mlkit.vision.label.ImageLabeling;
|
|
19
|
+
import com.google.mlkit.vision.label.defaults.ImageLabelerOptions;
|
|
17
20
|
import com.google.mlkit.vision.text.TextRecognition;
|
|
18
21
|
import com.google.mlkit.vision.text.TextRecognizer;
|
|
19
22
|
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
|
|
@@ -38,10 +41,11 @@ final class MlKitModelsModuleInstaller {
|
|
|
38
41
|
private MlKitModelsModuleInstaller() { }
|
|
39
42
|
|
|
40
43
|
static void checkAvailability(Activity activity, boolean includeFace, boolean includeBarcode,
|
|
41
|
-
boolean includeText,
|
|
44
|
+
boolean includeText, boolean includeImageLabeling,
|
|
45
|
+
AvailabilityCallback callback) {
|
|
42
46
|
JSONObject result = new JSONObject();
|
|
43
47
|
int requestedCount = (includeFace ? 1 : 0) + (includeBarcode ? 1 : 0)
|
|
44
|
-
+ (includeText ? 1 : 0);
|
|
48
|
+
+ (includeText ? 1 : 0) + (includeImageLabeling ? 1 : 0);
|
|
45
49
|
if (requestedCount == 0) {
|
|
46
50
|
callback.onResult(result);
|
|
47
51
|
return;
|
|
@@ -64,6 +68,13 @@ final class MlKitModelsModuleInstaller {
|
|
|
64
68
|
checkOne(client, "text", (OptionalModuleApi) text, text::close,
|
|
65
69
|
result, remaining, callback);
|
|
66
70
|
}
|
|
71
|
+
if (includeImageLabeling) {
|
|
72
|
+
ImageLabeler imageLabeling = ImageLabeling.getClient(
|
|
73
|
+
ImageLabelerOptions.DEFAULT_OPTIONS
|
|
74
|
+
);
|
|
75
|
+
checkOne(client, "imageLabeling", (OptionalModuleApi) imageLabeling,
|
|
76
|
+
imageLabeling::close, result, remaining, callback);
|
|
77
|
+
}
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
private static void checkOne(ModuleInstallClient client, String name, OptionalModuleApi module,
|
|
@@ -95,36 +106,39 @@ final class MlKitModelsModuleInstaller {
|
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
static void prepare(Activity activity, boolean includeFace, boolean includeBarcode,
|
|
98
|
-
boolean includeText, String loadingMessage,
|
|
109
|
+
boolean includeText, boolean includeImageLabeling, String loadingMessage,
|
|
99
110
|
MlKitLocalization localization, Callback callback) {
|
|
100
111
|
final FaceDetector face = includeFace
|
|
101
112
|
? FaceDetection.getClient(new FaceDetectorOptions.Builder().build()) : null;
|
|
102
113
|
final BarcodeScanner barcode = includeBarcode ? BarcodeScanning.getClient() : null;
|
|
103
114
|
final TextRecognizer text = includeText
|
|
104
115
|
? TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) : null;
|
|
116
|
+
final ImageLabeler imageLabeling = includeImageLabeling
|
|
117
|
+
? ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS) : null;
|
|
105
118
|
List<OptionalModuleApi> selectedModules = new ArrayList<>();
|
|
106
119
|
if (face != null) selectedModules.add((OptionalModuleApi) face);
|
|
107
120
|
if (barcode != null) selectedModules.add((OptionalModuleApi) barcode);
|
|
108
121
|
if (text != null) selectedModules.add((OptionalModuleApi) text);
|
|
122
|
+
if (imageLabeling != null) selectedModules.add((OptionalModuleApi) imageLabeling);
|
|
109
123
|
OptionalModuleApi[] modules = selectedModules.toArray(new OptionalModuleApi[0]);
|
|
110
124
|
ModuleInstallClient client = ModuleInstall.getClient(activity);
|
|
111
125
|
|
|
112
126
|
client.areModulesAvailable(modules)
|
|
113
127
|
.addOnSuccessListener(response -> {
|
|
114
128
|
if (response.areModulesAvailable()) {
|
|
115
|
-
close(face, barcode, text);
|
|
129
|
+
close(face, barcode, text, imageLabeling);
|
|
116
130
|
callback.onReady(readyResult(true));
|
|
117
131
|
} else {
|
|
118
132
|
MlKitLoadingDialog loadingDialog = new MlKitLoadingDialog(
|
|
119
133
|
activity, loadingMessage
|
|
120
134
|
);
|
|
121
135
|
loadingDialog.show();
|
|
122
|
-
install(client, modules, face, barcode, text, loadingDialog,
|
|
136
|
+
install(client, modules, face, barcode, text, imageLabeling, loadingDialog,
|
|
123
137
|
localization, callback);
|
|
124
138
|
}
|
|
125
139
|
})
|
|
126
140
|
.addOnFailureListener(error -> {
|
|
127
|
-
close(face, barcode, text);
|
|
141
|
+
close(face, barcode, text, imageLabeling);
|
|
128
142
|
callback.onError(MlKitSuitePlugin.error(
|
|
129
143
|
"MODULE_DOWNLOAD_FAILED",
|
|
130
144
|
localization.text("models_check_failed"),
|
|
@@ -135,6 +149,7 @@ final class MlKitModelsModuleInstaller {
|
|
|
135
149
|
|
|
136
150
|
private static void install(ModuleInstallClient client, OptionalModuleApi[] modules,
|
|
137
151
|
FaceDetector face, BarcodeScanner barcode, TextRecognizer text,
|
|
152
|
+
ImageLabeler imageLabeling,
|
|
138
153
|
MlKitLoadingDialog loadingDialog,
|
|
139
154
|
MlKitLocalization localization, Callback callback) {
|
|
140
155
|
AtomicBoolean finished = new AtomicBoolean(false);
|
|
@@ -142,16 +157,16 @@ final class MlKitModelsModuleInstaller {
|
|
|
142
157
|
listenerHolder[0] = update -> {
|
|
143
158
|
int state = update.getInstallState();
|
|
144
159
|
if (state == ModuleInstallStatusUpdate.InstallState.STATE_COMPLETED) {
|
|
145
|
-
finishSuccess(client, listenerHolder[0], face, barcode, text,
|
|
160
|
+
finishSuccess(client, listenerHolder[0], face, barcode, text, imageLabeling,
|
|
146
161
|
loadingDialog, callback, finished, false);
|
|
147
162
|
} else if (state == ModuleInstallStatusUpdate.InstallState.STATE_CANCELED) {
|
|
148
|
-
finishError(client, listenerHolder[0], face, barcode, text, loadingDialog,
|
|
163
|
+
finishError(client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog,
|
|
149
164
|
callback, finished,
|
|
150
165
|
"MODULE_DOWNLOAD_CANCELLED",
|
|
151
166
|
localization.text("models_download_cancelled"));
|
|
152
167
|
} else if (state == ModuleInstallStatusUpdate.InstallState.STATE_FAILED) {
|
|
153
168
|
String status = ModuleInstallStatusCodes.getStatusCodeString(update.getErrorCode());
|
|
154
|
-
finishError(client, listenerHolder[0], face, barcode, text, loadingDialog,
|
|
169
|
+
finishError(client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog,
|
|
155
170
|
callback, finished,
|
|
156
171
|
"MODULE_DOWNLOAD_FAILED",
|
|
157
172
|
localization.format("models_download_failed", status));
|
|
@@ -164,44 +179,48 @@ final class MlKitModelsModuleInstaller {
|
|
|
164
179
|
client.installModules(request)
|
|
165
180
|
.addOnSuccessListener(response -> {
|
|
166
181
|
if (response.areModulesAlreadyInstalled()) {
|
|
167
|
-
finishSuccess(client, listenerHolder[0], face, barcode, text,
|
|
182
|
+
finishSuccess(client, listenerHolder[0], face, barcode, text, imageLabeling,
|
|
168
183
|
loadingDialog, callback, finished, true);
|
|
169
184
|
}
|
|
170
185
|
})
|
|
171
186
|
.addOnFailureListener(error -> finishError(
|
|
172
|
-
client, listenerHolder[0], face, barcode, text, loadingDialog, callback, finished,
|
|
187
|
+
client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog, callback, finished,
|
|
173
188
|
"MODULE_DOWNLOAD_FAILED", localization.text("models_download_start_failed")
|
|
174
189
|
));
|
|
175
190
|
}
|
|
176
191
|
|
|
177
192
|
private static void finishSuccess(ModuleInstallClient client, InstallStatusListener listener,
|
|
178
193
|
FaceDetector face, BarcodeScanner barcode,
|
|
179
|
-
TextRecognizer text,
|
|
194
|
+
TextRecognizer text, ImageLabeler imageLabeling,
|
|
195
|
+
MlKitLoadingDialog loadingDialog,
|
|
180
196
|
Callback callback,
|
|
181
197
|
AtomicBoolean finished, boolean alreadyAvailable) {
|
|
182
198
|
if (!finished.compareAndSet(false, true)) return;
|
|
183
199
|
client.unregisterListener(listener);
|
|
184
200
|
loadingDialog.dismiss();
|
|
185
|
-
close(face, barcode, text);
|
|
201
|
+
close(face, barcode, text, imageLabeling);
|
|
186
202
|
callback.onReady(readyResult(alreadyAvailable));
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
private static void finishError(ModuleInstallClient client, InstallStatusListener listener,
|
|
190
206
|
FaceDetector face, BarcodeScanner barcode,
|
|
191
|
-
TextRecognizer text,
|
|
207
|
+
TextRecognizer text, ImageLabeler imageLabeling,
|
|
208
|
+
MlKitLoadingDialog loadingDialog,
|
|
192
209
|
Callback callback,
|
|
193
210
|
AtomicBoolean finished, String code, String message) {
|
|
194
211
|
if (!finished.compareAndSet(false, true)) return;
|
|
195
212
|
client.unregisterListener(listener);
|
|
196
213
|
loadingDialog.dismiss();
|
|
197
|
-
close(face, barcode, text);
|
|
214
|
+
close(face, barcode, text, imageLabeling);
|
|
198
215
|
callback.onError(MlKitSuitePlugin.error(code, message, true));
|
|
199
216
|
}
|
|
200
217
|
|
|
201
|
-
private static void close(FaceDetector face, BarcodeScanner barcode, TextRecognizer text
|
|
218
|
+
private static void close(FaceDetector face, BarcodeScanner barcode, TextRecognizer text,
|
|
219
|
+
ImageLabeler imageLabeling) {
|
|
202
220
|
if (face != null) face.close();
|
|
203
221
|
if (barcode != null) barcode.close();
|
|
204
222
|
if (text != null) text.close();
|
|
223
|
+
if (imageLabeling != null) imageLabeling.close();
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
private static JSONObject readyResult(boolean alreadyAvailable) {
|