@capgo/camera-preview 7.4.0-beta.10 → 7.4.0-beta.12
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/README.md +3 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +281 -133
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2028 -1467
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +71 -58
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +151 -72
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +3 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js +51 -34
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +51 -34
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +51 -34
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +212 -274
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +467 -390
- package/package.json +1 -1
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
package com.ahm.capacitor.camera.preview;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.graphics.Bitmap;
|
|
6
|
+
import android.graphics.BitmapFactory;
|
|
7
|
+
import android.graphics.Matrix;
|
|
4
8
|
import android.hardware.camera2.CameraAccessException;
|
|
9
|
+
import android.hardware.camera2.CameraCharacteristics;
|
|
5
10
|
import android.hardware.camera2.CameraManager;
|
|
11
|
+
import android.location.Location;
|
|
12
|
+
import android.net.Uri;
|
|
6
13
|
import android.os.Build;
|
|
14
|
+
import android.os.Environment;
|
|
7
15
|
import android.util.Base64;
|
|
8
16
|
import android.util.DisplayMetrics;
|
|
9
17
|
import android.util.Log;
|
|
18
|
+
import android.util.Rational;
|
|
10
19
|
import android.util.Size;
|
|
11
20
|
import android.view.ViewGroup;
|
|
21
|
+
import android.view.ViewGroup;
|
|
12
22
|
import android.webkit.WebView;
|
|
23
|
+
import android.widget.FrameLayout;
|
|
24
|
+
import android.widget.FrameLayout;
|
|
13
25
|
import androidx.annotation.NonNull;
|
|
14
26
|
import androidx.annotation.OptIn;
|
|
27
|
+
import androidx.camera.camera2.interop.Camera2CameraInfo;
|
|
28
|
+
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
|
|
29
|
+
import androidx.camera.core.AspectRatio;
|
|
15
30
|
import androidx.camera.core.Camera;
|
|
16
31
|
import androidx.camera.core.CameraInfo;
|
|
17
32
|
import androidx.camera.core.CameraSelector;
|
|
@@ -19,1630 +34,2176 @@ import androidx.camera.core.ImageCapture;
|
|
|
19
34
|
import androidx.camera.core.ImageCaptureException;
|
|
20
35
|
import androidx.camera.core.ImageProxy;
|
|
21
36
|
import androidx.camera.core.Preview;
|
|
22
|
-
import androidx.camera.core.
|
|
37
|
+
import androidx.camera.core.ResolutionInfo;
|
|
38
|
+
import androidx.camera.core.ZoomState;
|
|
23
39
|
import androidx.camera.core.resolutionselector.AspectRatioStrategy;
|
|
24
40
|
import androidx.camera.core.resolutionselector.ResolutionSelector;
|
|
25
41
|
import androidx.camera.core.resolutionselector.ResolutionStrategy;
|
|
26
42
|
import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
27
43
|
import androidx.camera.view.PreviewView;
|
|
28
44
|
import androidx.core.content.ContextCompat;
|
|
45
|
+
import androidx.exifinterface.media.ExifInterface;
|
|
29
46
|
import androidx.lifecycle.Lifecycle;
|
|
47
|
+
import androidx.lifecycle.LifecycleObserver;
|
|
30
48
|
import androidx.lifecycle.LifecycleOwner;
|
|
31
49
|
import androidx.lifecycle.LifecycleRegistry;
|
|
50
|
+
import androidx.lifecycle.OnLifecycleEvent;
|
|
32
51
|
import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
|
|
33
|
-
import android.widget.FrameLayout;
|
|
34
52
|
import com.ahm.capacitor.camera.preview.model.LensInfo;
|
|
35
53
|
import com.ahm.capacitor.camera.preview.model.ZoomFactors;
|
|
36
54
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
55
|
+
import java.io.ByteArrayOutputStream;
|
|
56
|
+
import java.io.File;
|
|
57
|
+
import java.io.FileOutputStream;
|
|
58
|
+
import java.io.IOException;
|
|
37
59
|
import java.nio.ByteBuffer;
|
|
60
|
+
import java.nio.file.Files;
|
|
61
|
+
import java.text.SimpleDateFormat;
|
|
62
|
+
import java.util.ArrayList;
|
|
38
63
|
import java.util.Arrays;
|
|
39
64
|
import java.util.Collections;
|
|
40
65
|
import java.util.List;
|
|
41
|
-
import java.util.
|
|
66
|
+
import java.util.Locale;
|
|
42
67
|
import java.util.Objects;
|
|
68
|
+
import java.util.Set;
|
|
43
69
|
import java.util.concurrent.Executor;
|
|
44
70
|
import java.util.concurrent.ExecutorService;
|
|
45
71
|
import java.util.concurrent.Executors;
|
|
46
|
-
import androidx.camera.camera2.interop.Camera2CameraInfo;
|
|
47
|
-
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
|
|
48
|
-
import android.hardware.camera2.CameraCharacteristics;
|
|
49
|
-
import java.util.Set;
|
|
50
|
-
import androidx.camera.core.ZoomState;
|
|
51
|
-
import androidx.camera.core.ResolutionInfo;
|
|
52
|
-
import android.content.Intent;
|
|
53
|
-
import android.net.Uri;
|
|
54
|
-
import android.os.Environment;
|
|
55
|
-
import java.io.File;
|
|
56
|
-
import java.io.FileOutputStream;
|
|
57
|
-
import java.io.IOException;
|
|
58
|
-
import java.text.SimpleDateFormat;
|
|
59
|
-
import java.util.Locale;
|
|
60
|
-
import androidx.exifinterface.media.ExifInterface;
|
|
61
72
|
import org.json.JSONObject;
|
|
62
|
-
import java.nio.file.Files;
|
|
63
|
-
import android.graphics.BitmapFactory;
|
|
64
|
-
import android.graphics.Bitmap;
|
|
65
|
-
import android.graphics.Matrix;
|
|
66
|
-
import java.io.ByteArrayOutputStream;
|
|
67
|
-
import android.location.Location;
|
|
68
|
-
import android.widget.FrameLayout;
|
|
69
|
-
import androidx.lifecycle.LifecycleObserver;
|
|
70
|
-
import androidx.lifecycle.OnLifecycleEvent;
|
|
71
|
-
import android.util.Rational;
|
|
72
|
-
import android.view.ViewGroup;
|
|
73
73
|
|
|
74
74
|
public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
75
|
-
private static final String TAG = "CameraPreview CameraXView";
|
|
76
|
-
|
|
77
|
-
public interface CameraXViewListener {
|
|
78
|
-
void onPictureTaken(String base64, JSONObject exif);
|
|
79
|
-
void onPictureTakenError(String message);
|
|
80
|
-
void onSampleTaken(String result);
|
|
81
|
-
void onSampleTakenError(String message);
|
|
82
|
-
void onCameraStarted(int width, int height, int x, int y);
|
|
83
|
-
void onCameraStartError(String message);
|
|
84
|
-
}
|
|
85
75
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
76
|
+
private static final String TAG = "CameraPreview CameraXView";
|
|
77
|
+
|
|
78
|
+
public interface CameraXViewListener {
|
|
79
|
+
void onPictureTaken(String base64, JSONObject exif);
|
|
80
|
+
void onPictureTakenError(String message);
|
|
81
|
+
void onSampleTaken(String result);
|
|
82
|
+
void onSampleTakenError(String message);
|
|
83
|
+
void onCameraStarted(int width, int height, int x, int y);
|
|
84
|
+
void onCameraStartError(String message);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private ProcessCameraProvider cameraProvider;
|
|
88
|
+
private Camera camera;
|
|
89
|
+
private ImageCapture imageCapture;
|
|
90
|
+
private ImageCapture sampleImageCapture;
|
|
91
|
+
private PreviewView previewView;
|
|
92
|
+
private GridOverlayView gridOverlayView;
|
|
93
|
+
private FrameLayout previewContainer;
|
|
94
|
+
private CameraSelector currentCameraSelector;
|
|
95
|
+
private String currentDeviceId;
|
|
96
|
+
private int currentFlashMode = ImageCapture.FLASH_MODE_OFF;
|
|
97
|
+
private CameraSessionConfiguration sessionConfig;
|
|
98
|
+
private CameraXViewListener listener;
|
|
99
|
+
private final Context context;
|
|
100
|
+
private final WebView webView;
|
|
101
|
+
private final LifecycleRegistry lifecycleRegistry;
|
|
102
|
+
private final Executor mainExecutor;
|
|
103
|
+
private ExecutorService cameraExecutor;
|
|
104
|
+
private boolean isRunning = false;
|
|
105
|
+
|
|
106
|
+
public CameraXView(Context context, WebView webView) {
|
|
107
|
+
this.context = context;
|
|
108
|
+
this.webView = webView;
|
|
109
|
+
this.lifecycleRegistry = new LifecycleRegistry(this);
|
|
110
|
+
this.mainExecutor = ContextCompat.getMainExecutor(context);
|
|
111
|
+
|
|
112
|
+
mainExecutor.execute(() ->
|
|
113
|
+
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@NonNull
|
|
118
|
+
@Override
|
|
119
|
+
public Lifecycle getLifecycle() {
|
|
120
|
+
return lifecycleRegistry;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public void setListener(CameraXViewListener listener) {
|
|
124
|
+
this.listener = listener;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public boolean isRunning() {
|
|
128
|
+
return isRunning;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private void saveImageToGallery(byte[] data) {
|
|
132
|
+
try {
|
|
133
|
+
File photo = new File(
|
|
134
|
+
Environment.getExternalStoragePublicDirectory(
|
|
135
|
+
Environment.DIRECTORY_PICTURES
|
|
136
|
+
),
|
|
137
|
+
"IMG_" +
|
|
138
|
+
new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(
|
|
139
|
+
new java.util.Date()
|
|
140
|
+
) +
|
|
141
|
+
".jpg"
|
|
142
|
+
);
|
|
143
|
+
FileOutputStream fos = new FileOutputStream(photo);
|
|
144
|
+
fos.write(data);
|
|
145
|
+
fos.close();
|
|
146
|
+
|
|
147
|
+
// Notify the gallery of the new image
|
|
148
|
+
Intent mediaScanIntent = new Intent(
|
|
149
|
+
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
|
150
|
+
);
|
|
151
|
+
Uri contentUri = Uri.fromFile(photo);
|
|
152
|
+
mediaScanIntent.setData(contentUri);
|
|
153
|
+
context.sendBroadcast(mediaScanIntent);
|
|
154
|
+
} catch (IOException e) {
|
|
155
|
+
Log.e(TAG, "Error saving image to gallery", e);
|
|
112
156
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public void startSession(CameraSessionConfiguration config) {
|
|
160
|
+
this.sessionConfig = config;
|
|
161
|
+
cameraExecutor = Executors.newSingleThreadExecutor();
|
|
162
|
+
mainExecutor.execute(() -> {
|
|
163
|
+
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
|
164
|
+
setupCamera();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public void stopSession() {
|
|
169
|
+
isRunning = false;
|
|
170
|
+
mainExecutor.execute(() -> {
|
|
171
|
+
if (cameraProvider != null) {
|
|
172
|
+
cameraProvider.unbindAll();
|
|
173
|
+
}
|
|
174
|
+
lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
|
175
|
+
if (cameraExecutor != null) {
|
|
176
|
+
cameraExecutor.shutdownNow();
|
|
177
|
+
}
|
|
178
|
+
removePreviewView();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private void setupCamera() {
|
|
183
|
+
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
|
|
184
|
+
ProcessCameraProvider.getInstance(context);
|
|
185
|
+
cameraProviderFuture.addListener(
|
|
186
|
+
() -> {
|
|
187
|
+
try {
|
|
188
|
+
cameraProvider = cameraProviderFuture.get();
|
|
189
|
+
setupPreviewView();
|
|
190
|
+
bindCameraUseCases();
|
|
191
|
+
} catch (Exception e) {
|
|
192
|
+
if (listener != null) {
|
|
193
|
+
listener.onCameraStartError(
|
|
194
|
+
"Error initializing camera: " + e.getMessage()
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
mainExecutor
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private void setupPreviewView() {
|
|
204
|
+
if (previewView != null) {
|
|
205
|
+
removePreviewView();
|
|
118
206
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.listener = listener;
|
|
207
|
+
if (sessionConfig.isToBack()) {
|
|
208
|
+
webView.setBackgroundColor(android.graphics.Color.TRANSPARENT);
|
|
122
209
|
}
|
|
123
210
|
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
// Create a container to hold both the preview and grid overlay
|
|
212
|
+
previewContainer = new FrameLayout(context);
|
|
213
|
+
|
|
214
|
+
// Create and setup the preview view
|
|
215
|
+
previewView = new PreviewView(context);
|
|
216
|
+
previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
|
|
217
|
+
previewContainer.addView(
|
|
218
|
+
previewView,
|
|
219
|
+
new FrameLayout.LayoutParams(
|
|
220
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
221
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Create and setup the grid overlay
|
|
226
|
+
gridOverlayView = new GridOverlayView(context);
|
|
227
|
+
previewContainer.addView(
|
|
228
|
+
gridOverlayView,
|
|
229
|
+
new FrameLayout.LayoutParams(
|
|
230
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
231
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
// Set grid mode after adding to container to ensure proper layout
|
|
235
|
+
gridOverlayView.post(() -> {
|
|
236
|
+
String currentGridMode = sessionConfig.getGridMode();
|
|
237
|
+
Log.d(TAG, "setupPreviewView: Setting grid mode to: " + currentGridMode);
|
|
238
|
+
gridOverlayView.setGridMode(currentGridMode);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
ViewGroup parent = (ViewGroup) webView.getParent();
|
|
242
|
+
if (parent != null) {
|
|
243
|
+
FrameLayout.LayoutParams layoutParams = calculatePreviewLayoutParams();
|
|
244
|
+
parent.addView(previewContainer, layoutParams);
|
|
245
|
+
if (sessionConfig.isToBack()) webView.bringToFront();
|
|
126
246
|
}
|
|
127
|
-
|
|
128
|
-
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private FrameLayout.LayoutParams calculatePreviewLayoutParams() {
|
|
250
|
+
// sessionConfig already contains pixel-converted coordinates with webview offsets applied
|
|
251
|
+
int x = sessionConfig.getX();
|
|
252
|
+
int y = sessionConfig.getY();
|
|
253
|
+
int width = sessionConfig.getWidth();
|
|
254
|
+
int height = sessionConfig.getHeight();
|
|
255
|
+
String aspectRatio = sessionConfig.getAspectRatio();
|
|
256
|
+
|
|
257
|
+
Log.d(
|
|
258
|
+
TAG,
|
|
259
|
+
"calculatePreviewLayoutParams: Using sessionConfig values - x:" +
|
|
260
|
+
x +
|
|
261
|
+
" y:" +
|
|
262
|
+
y +
|
|
263
|
+
" width:" +
|
|
264
|
+
width +
|
|
265
|
+
" height:" +
|
|
266
|
+
height +
|
|
267
|
+
" aspectRatio:" +
|
|
268
|
+
aspectRatio
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Apply aspect ratio if specified and no explicit size was given
|
|
272
|
+
if (aspectRatio != null && !aspectRatio.isEmpty()) {
|
|
273
|
+
String[] ratios = aspectRatio.split(":");
|
|
274
|
+
if (ratios.length == 2) {
|
|
129
275
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
276
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
277
|
+
float ratio =
|
|
278
|
+
Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
|
|
279
|
+
|
|
280
|
+
// Calculate optimal size while maintaining aspect ratio
|
|
281
|
+
int optimalWidth = width;
|
|
282
|
+
int optimalHeight = (int) (width / ratio);
|
|
283
|
+
|
|
284
|
+
if (optimalHeight > height) {
|
|
285
|
+
// Height constraint is tighter, fit by height
|
|
286
|
+
optimalHeight = height;
|
|
287
|
+
optimalWidth = (int) (height * ratio);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
width = optimalWidth;
|
|
291
|
+
height = optimalHeight;
|
|
292
|
+
Log.d(
|
|
293
|
+
TAG,
|
|
294
|
+
"calculatePreviewLayoutParams: Applied aspect ratio " +
|
|
295
|
+
aspectRatio +
|
|
296
|
+
" - new size: " +
|
|
297
|
+
width +
|
|
298
|
+
"x" +
|
|
299
|
+
height
|
|
300
|
+
);
|
|
301
|
+
} catch (NumberFormatException e) {
|
|
302
|
+
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
145
303
|
}
|
|
304
|
+
}
|
|
146
305
|
}
|
|
147
306
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
307
|
+
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
|
308
|
+
width,
|
|
309
|
+
height
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Only add insets for positioning coordinates, not for full-screen sizes
|
|
313
|
+
int webViewTopInset = getWebViewTopInset();
|
|
314
|
+
int webViewLeftInset = getWebViewLeftInset();
|
|
315
|
+
|
|
316
|
+
// Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
|
|
317
|
+
if (x == 0 && y == 0) {
|
|
318
|
+
layoutParams.leftMargin = x;
|
|
319
|
+
layoutParams.topMargin = y;
|
|
320
|
+
Log.d(
|
|
321
|
+
TAG,
|
|
322
|
+
"calculatePreviewLayoutParams: Full-screen mode - keeping position (0,0) without insets"
|
|
323
|
+
);
|
|
324
|
+
} else {
|
|
325
|
+
layoutParams.leftMargin = x + webViewLeftInset;
|
|
326
|
+
layoutParams.topMargin = y + webViewTopInset;
|
|
327
|
+
Log.d(
|
|
328
|
+
TAG,
|
|
329
|
+
"calculatePreviewLayoutParams: Positioned mode - applying insets"
|
|
330
|
+
);
|
|
155
331
|
}
|
|
156
332
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
333
|
+
Log.d(
|
|
334
|
+
TAG,
|
|
335
|
+
"calculatePreviewLayoutParams: Applied insets - x:" +
|
|
336
|
+
x +
|
|
337
|
+
"+" +
|
|
338
|
+
webViewLeftInset +
|
|
339
|
+
"=" +
|
|
340
|
+
layoutParams.leftMargin +
|
|
341
|
+
", y:" +
|
|
342
|
+
y +
|
|
343
|
+
"+" +
|
|
344
|
+
webViewTopInset +
|
|
345
|
+
"=" +
|
|
346
|
+
layoutParams.topMargin
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
Log.d(
|
|
350
|
+
TAG,
|
|
351
|
+
"calculatePreviewLayoutParams: Final layout - x:" +
|
|
352
|
+
x +
|
|
353
|
+
" y:" +
|
|
354
|
+
y +
|
|
355
|
+
" width:" +
|
|
356
|
+
width +
|
|
357
|
+
" height:" +
|
|
358
|
+
height
|
|
359
|
+
);
|
|
360
|
+
return layoutParams;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private void removePreviewView() {
|
|
364
|
+
if (previewContainer != null) {
|
|
365
|
+
ViewGroup parent = (ViewGroup) previewContainer.getParent();
|
|
366
|
+
if (parent != null) {
|
|
367
|
+
parent.removeView(previewContainer);
|
|
368
|
+
}
|
|
369
|
+
previewContainer = null;
|
|
169
370
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context);
|
|
173
|
-
cameraProviderFuture.addListener(() -> {
|
|
174
|
-
try {
|
|
175
|
-
cameraProvider = cameraProviderFuture.get();
|
|
176
|
-
setupPreviewView();
|
|
177
|
-
bindCameraUseCases();
|
|
178
|
-
} catch (Exception e) {
|
|
179
|
-
if (listener != null) {
|
|
180
|
-
listener.onCameraStartError("Error initializing camera: " + e.getMessage());
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}, mainExecutor);
|
|
371
|
+
if (previewView != null) {
|
|
372
|
+
previewView = null;
|
|
184
373
|
}
|
|
374
|
+
if (gridOverlayView != null) {
|
|
375
|
+
gridOverlayView = null;
|
|
376
|
+
}
|
|
377
|
+
webView.setBackgroundColor(android.graphics.Color.WHITE);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
381
|
+
private void bindCameraUseCases() {
|
|
382
|
+
if (cameraProvider == null) return;
|
|
383
|
+
mainExecutor.execute(() -> {
|
|
384
|
+
try {
|
|
385
|
+
Log.d(
|
|
386
|
+
TAG,
|
|
387
|
+
"Building camera selector with deviceId: " +
|
|
388
|
+
sessionConfig.getDeviceId() +
|
|
389
|
+
" and position: " +
|
|
390
|
+
sessionConfig.getPosition()
|
|
391
|
+
);
|
|
392
|
+
currentCameraSelector = buildCameraSelector();
|
|
185
393
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
394
|
+
ResolutionSelector.Builder resolutionSelectorBuilder =
|
|
395
|
+
new ResolutionSelector.Builder()
|
|
396
|
+
.setResolutionStrategy(
|
|
397
|
+
ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
if (sessionConfig.getAspectRatio() != null) {
|
|
401
|
+
int aspectRatio;
|
|
402
|
+
if ("16:9".equals(sessionConfig.getAspectRatio())) {
|
|
403
|
+
aspectRatio = AspectRatio.RATIO_16_9;
|
|
404
|
+
} else { // "4:3"
|
|
405
|
+
aspectRatio = AspectRatio.RATIO_4_3;
|
|
406
|
+
}
|
|
407
|
+
resolutionSelectorBuilder.setAspectRatioStrategy(
|
|
408
|
+
new AspectRatioStrategy(
|
|
409
|
+
aspectRatio,
|
|
410
|
+
AspectRatioStrategy.FALLBACK_RULE_AUTO
|
|
411
|
+
)
|
|
412
|
+
);
|
|
192
413
|
}
|
|
193
414
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
415
|
+
ResolutionSelector resolutionSelector =
|
|
416
|
+
resolutionSelectorBuilder.build();
|
|
417
|
+
|
|
418
|
+
Preview preview = new Preview.Builder()
|
|
419
|
+
.setResolutionSelector(resolutionSelector)
|
|
420
|
+
.build();
|
|
421
|
+
imageCapture = new ImageCapture.Builder()
|
|
422
|
+
.setResolutionSelector(resolutionSelector)
|
|
423
|
+
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
424
|
+
.setFlashMode(currentFlashMode)
|
|
425
|
+
.build();
|
|
426
|
+
sampleImageCapture = imageCapture;
|
|
427
|
+
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
428
|
+
// Unbind any existing use cases and bind new ones
|
|
429
|
+
cameraProvider.unbindAll();
|
|
430
|
+
camera = cameraProvider.bindToLifecycle(
|
|
431
|
+
this,
|
|
432
|
+
currentCameraSelector,
|
|
433
|
+
preview,
|
|
434
|
+
imageCapture
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Log details about the active camera
|
|
438
|
+
Log.d(TAG, "Use cases bound. Inspecting active camera and use cases.");
|
|
439
|
+
CameraInfo cameraInfo = camera.getCameraInfo();
|
|
440
|
+
Log.d(
|
|
441
|
+
TAG,
|
|
442
|
+
"Bound Camera ID: " + Camera2CameraInfo.from(cameraInfo).getCameraId()
|
|
443
|
+
);
|
|
217
444
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
445
|
+
// Log zoom state
|
|
446
|
+
ZoomState zoomState = cameraInfo.getZoomState().getValue();
|
|
447
|
+
if (zoomState != null) {
|
|
448
|
+
Log.d(
|
|
449
|
+
TAG,
|
|
450
|
+
"Active Zoom State: " +
|
|
451
|
+
"min=" +
|
|
452
|
+
zoomState.getMinZoomRatio() +
|
|
453
|
+
", " +
|
|
454
|
+
"max=" +
|
|
455
|
+
zoomState.getMaxZoomRatio() +
|
|
456
|
+
", " +
|
|
457
|
+
"current=" +
|
|
458
|
+
zoomState.getZoomRatio()
|
|
459
|
+
);
|
|
223
460
|
}
|
|
224
|
-
}
|
|
225
461
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
242
|
-
float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
|
|
243
|
-
|
|
244
|
-
// Calculate optimal size while maintaining aspect ratio
|
|
245
|
-
int optimalWidth = width;
|
|
246
|
-
int optimalHeight = (int) (width / ratio);
|
|
247
|
-
|
|
248
|
-
if (optimalHeight > height) {
|
|
249
|
-
// Height constraint is tighter, fit by height
|
|
250
|
-
optimalHeight = height;
|
|
251
|
-
optimalWidth = (int) (height * ratio);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
width = optimalWidth;
|
|
255
|
-
height = optimalHeight;
|
|
256
|
-
Log.d(TAG, "calculatePreviewLayoutParams: Applied aspect ratio " + aspectRatio + " - new size: " + width + "x" + height);
|
|
257
|
-
} catch (NumberFormatException e) {
|
|
258
|
-
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
462
|
+
// Log physical cameras of the active camera
|
|
463
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
464
|
+
Set<CameraInfo> physicalCameras = cameraInfo.getPhysicalCameraInfos();
|
|
465
|
+
Log.d(
|
|
466
|
+
TAG,
|
|
467
|
+
"Active camera has " + physicalCameras.size() + " physical cameras."
|
|
468
|
+
);
|
|
469
|
+
for (CameraInfo physical : physicalCameras) {
|
|
470
|
+
Log.d(
|
|
471
|
+
TAG,
|
|
472
|
+
" - Physical camera ID: " +
|
|
473
|
+
Camera2CameraInfo.from(physical).getCameraId()
|
|
474
|
+
);
|
|
475
|
+
}
|
|
261
476
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (x == 0 && y == 0) {
|
|
271
|
-
layoutParams.leftMargin = x;
|
|
272
|
-
layoutParams.topMargin = y;
|
|
273
|
-
Log.d(TAG, "calculatePreviewLayoutParams: Full-screen mode - keeping position (0,0) without insets");
|
|
274
|
-
} else {
|
|
275
|
-
layoutParams.leftMargin = x + webViewLeftInset;
|
|
276
|
-
layoutParams.topMargin = y + webViewTopInset;
|
|
277
|
-
Log.d(TAG, "calculatePreviewLayoutParams: Positioned mode - applying insets");
|
|
477
|
+
|
|
478
|
+
// Log resolution info
|
|
479
|
+
ResolutionInfo previewResolution = preview.getResolutionInfo();
|
|
480
|
+
if (previewResolution != null) {
|
|
481
|
+
Log.d(
|
|
482
|
+
TAG,
|
|
483
|
+
"Preview resolution: " + previewResolution.getResolution()
|
|
484
|
+
);
|
|
278
485
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
private void removePreviewView() {
|
|
288
|
-
if (previewContainer != null) {
|
|
289
|
-
ViewGroup parent = (ViewGroup) previewContainer.getParent();
|
|
290
|
-
if (parent != null) {
|
|
291
|
-
parent.removeView(previewContainer);
|
|
292
|
-
}
|
|
293
|
-
previewContainer = null;
|
|
486
|
+
ResolutionInfo imageCaptureResolution =
|
|
487
|
+
imageCapture.getResolutionInfo();
|
|
488
|
+
if (imageCaptureResolution != null) {
|
|
489
|
+
Log.d(
|
|
490
|
+
TAG,
|
|
491
|
+
"Image capture resolution: " +
|
|
492
|
+
imageCaptureResolution.getResolution()
|
|
493
|
+
);
|
|
294
494
|
}
|
|
295
|
-
|
|
296
|
-
|
|
495
|
+
|
|
496
|
+
// Set initial zoom if specified, prioritizing targetZoom over default zoomFactor
|
|
497
|
+
float initialZoom = sessionConfig.getTargetZoom() != 1.0f
|
|
498
|
+
? sessionConfig.getTargetZoom()
|
|
499
|
+
: sessionConfig.getZoomFactor();
|
|
500
|
+
if (initialZoom != 1.0f) {
|
|
501
|
+
Log.d(TAG, "Applying initial zoom of " + initialZoom);
|
|
502
|
+
setZoomInternal(initialZoom);
|
|
297
503
|
}
|
|
298
|
-
|
|
299
|
-
|
|
504
|
+
|
|
505
|
+
isRunning = true;
|
|
506
|
+
Log.d(TAG, "bindCameraUseCases: Camera bound successfully");
|
|
507
|
+
if (listener != null) {
|
|
508
|
+
// Post the callback to ensure layout is complete
|
|
509
|
+
previewContainer.post(() -> {
|
|
510
|
+
// Return actual preview container dimensions instead of requested dimensions
|
|
511
|
+
int actualWidth = previewContainer != null
|
|
512
|
+
? previewContainer.getWidth()
|
|
513
|
+
: sessionConfig.getWidth();
|
|
514
|
+
int actualHeight = previewContainer != null
|
|
515
|
+
? previewContainer.getHeight()
|
|
516
|
+
: sessionConfig.getHeight();
|
|
517
|
+
int actualX = previewContainer != null
|
|
518
|
+
? previewContainer.getLeft()
|
|
519
|
+
: sessionConfig.getX();
|
|
520
|
+
int actualY = previewContainer != null
|
|
521
|
+
? previewContainer.getTop()
|
|
522
|
+
: sessionConfig.getY();
|
|
523
|
+
listener.onCameraStarted(
|
|
524
|
+
actualWidth,
|
|
525
|
+
actualHeight,
|
|
526
|
+
actualX,
|
|
527
|
+
actualY
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
} catch (Exception e) {
|
|
532
|
+
if (listener != null) listener.onCameraStartError(
|
|
533
|
+
"Error binding camera: " + e.getMessage()
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
540
|
+
private CameraSelector buildCameraSelector() {
|
|
541
|
+
CameraSelector.Builder builder = new CameraSelector.Builder();
|
|
542
|
+
final String deviceId = sessionConfig.getDeviceId();
|
|
543
|
+
|
|
544
|
+
if (deviceId != null && !deviceId.isEmpty()) {
|
|
545
|
+
builder.addCameraFilter(cameraInfos -> {
|
|
546
|
+
for (CameraInfo cameraInfo : cameraInfos) {
|
|
547
|
+
if (
|
|
548
|
+
deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())
|
|
549
|
+
) {
|
|
550
|
+
return Collections.singletonList(cameraInfo);
|
|
551
|
+
}
|
|
300
552
|
}
|
|
301
|
-
|
|
553
|
+
return Collections.emptyList();
|
|
554
|
+
});
|
|
555
|
+
} else {
|
|
556
|
+
String position = sessionConfig.getPosition();
|
|
557
|
+
int requiredFacing = "front".equals(position)
|
|
558
|
+
? CameraSelector.LENS_FACING_FRONT
|
|
559
|
+
: CameraSelector.LENS_FACING_BACK;
|
|
560
|
+
builder.requireLensFacing(requiredFacing);
|
|
561
|
+
}
|
|
562
|
+
return builder.build();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private static String getCameraId(
|
|
566
|
+
androidx.camera.core.CameraInfo cameraInfo
|
|
567
|
+
) {
|
|
568
|
+
try {
|
|
569
|
+
// Generate a stable ID based on camera characteristics
|
|
570
|
+
boolean isBack = isBackCamera(cameraInfo);
|
|
571
|
+
float minZoom = Objects.requireNonNull(
|
|
572
|
+
cameraInfo.getZoomState().getValue()
|
|
573
|
+
).getMinZoomRatio();
|
|
574
|
+
float maxZoom = cameraInfo.getZoomState().getValue().getMaxZoomRatio();
|
|
575
|
+
|
|
576
|
+
// Create a unique ID based on camera properties
|
|
577
|
+
String position = isBack ? "back" : "front";
|
|
578
|
+
return position + "_" + minZoom + "_" + maxZoom;
|
|
579
|
+
} catch (Exception e) {
|
|
580
|
+
return "unknown_camera";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private static boolean isBackCamera(
|
|
585
|
+
androidx.camera.core.CameraInfo cameraInfo
|
|
586
|
+
) {
|
|
587
|
+
try {
|
|
588
|
+
// Check if this camera matches the back camera selector
|
|
589
|
+
CameraSelector backSelector = new CameraSelector.Builder()
|
|
590
|
+
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
|
591
|
+
.build();
|
|
592
|
+
|
|
593
|
+
// Try to filter cameras with back selector - if this camera is included, it's a back camera
|
|
594
|
+
List<androidx.camera.core.CameraInfo> backCameras = backSelector.filter(
|
|
595
|
+
Collections.singletonList(cameraInfo)
|
|
596
|
+
);
|
|
597
|
+
return !backCameras.isEmpty();
|
|
598
|
+
} catch (Exception e) {
|
|
599
|
+
Log.w(TAG, "Error determining camera direction, assuming back camera", e);
|
|
600
|
+
return true; // Default to back camera
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
public void capturePhoto(
|
|
605
|
+
int quality,
|
|
606
|
+
final boolean saveToGallery,
|
|
607
|
+
Integer width,
|
|
608
|
+
Integer height,
|
|
609
|
+
Location location
|
|
610
|
+
) {
|
|
611
|
+
Log.d(TAG, "capturePhoto: Starting photo capture with quality: " + quality);
|
|
612
|
+
|
|
613
|
+
if (imageCapture == null) {
|
|
614
|
+
if (listener != null) {
|
|
615
|
+
listener.onPictureTakenError("Camera not ready");
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
302
618
|
}
|
|
303
619
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
resolutionSelectorBuilder.setAspectRatioStrategy(new AspectRatioStrategy(aspectRatio, AspectRatioStrategy.FALLBACK_RULE_AUTO));
|
|
323
|
-
}
|
|
620
|
+
File tempFile = new File(context.getCacheDir(), "temp_image.jpg");
|
|
621
|
+
ImageCapture.OutputFileOptions outputFileOptions =
|
|
622
|
+
new ImageCapture.OutputFileOptions.Builder(tempFile).build();
|
|
623
|
+
|
|
624
|
+
imageCapture.takePicture(
|
|
625
|
+
outputFileOptions,
|
|
626
|
+
cameraExecutor,
|
|
627
|
+
new ImageCapture.OnImageSavedCallback() {
|
|
628
|
+
@Override
|
|
629
|
+
public void onError(@NonNull ImageCaptureException exception) {
|
|
630
|
+
Log.e(TAG, "capturePhoto: Photo capture failed", exception);
|
|
631
|
+
if (listener != null) {
|
|
632
|
+
listener.onPictureTakenError(
|
|
633
|
+
"Photo capture failed: " + exception.getMessage()
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
324
637
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
// Log details about the active camera
|
|
336
|
-
Log.d(TAG, "Use cases bound. Inspecting active camera and use cases.");
|
|
337
|
-
CameraInfo cameraInfo = camera.getCameraInfo();
|
|
338
|
-
Log.d(TAG, "Bound Camera ID: " + Camera2CameraInfo.from(cameraInfo).getCameraId());
|
|
339
|
-
|
|
340
|
-
// Log zoom state
|
|
341
|
-
ZoomState zoomState = cameraInfo.getZoomState().getValue();
|
|
342
|
-
if (zoomState != null) {
|
|
343
|
-
Log.d(TAG, "Active Zoom State: " +
|
|
344
|
-
"min=" + zoomState.getMinZoomRatio() + ", " +
|
|
345
|
-
"max=" + zoomState.getMaxZoomRatio() + ", " +
|
|
346
|
-
"current=" + zoomState.getZoomRatio());
|
|
347
|
-
}
|
|
638
|
+
@Override
|
|
639
|
+
public void onImageSaved(
|
|
640
|
+
@NonNull ImageCapture.OutputFileResults output
|
|
641
|
+
) {
|
|
642
|
+
try {
|
|
643
|
+
// Read file using FileInputStream for compatibility
|
|
644
|
+
byte[] bytes = new byte[(int) tempFile.length()];
|
|
645
|
+
java.io.FileInputStream fis = new java.io.FileInputStream(tempFile);
|
|
646
|
+
fis.read(bytes);
|
|
647
|
+
fis.close();
|
|
348
648
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
Log.d(TAG, "Active camera has " + physicalCameras.size() + " physical cameras.");
|
|
353
|
-
for (CameraInfo physical : physicalCameras) {
|
|
354
|
-
Log.d(TAG, " - Physical camera ID: " + Camera2CameraInfo.from(physical).getCameraId());
|
|
355
|
-
}
|
|
356
|
-
}
|
|
649
|
+
ExifInterface exifInterface = new ExifInterface(
|
|
650
|
+
tempFile.getAbsolutePath()
|
|
651
|
+
);
|
|
357
652
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
Log.d(TAG, "Preview resolution: " + previewResolution.getResolution());
|
|
362
|
-
}
|
|
363
|
-
ResolutionInfo imageCaptureResolution = imageCapture.getResolutionInfo();
|
|
364
|
-
if (imageCaptureResolution != null) {
|
|
365
|
-
Log.d(TAG, "Image capture resolution: " + imageCaptureResolution.getResolution());
|
|
366
|
-
}
|
|
653
|
+
if (location != null) {
|
|
654
|
+
exifInterface.setGpsInfo(location);
|
|
655
|
+
}
|
|
367
656
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
657
|
+
JSONObject exifData = getExifData(exifInterface);
|
|
658
|
+
|
|
659
|
+
if (width != null && height != null) {
|
|
660
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(
|
|
661
|
+
bytes,
|
|
662
|
+
0,
|
|
663
|
+
bytes.length
|
|
664
|
+
);
|
|
665
|
+
Bitmap resizedBitmap = resizeBitmap(bitmap, width, height);
|
|
666
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
667
|
+
resizedBitmap.compress(
|
|
668
|
+
Bitmap.CompressFormat.JPEG,
|
|
669
|
+
quality,
|
|
670
|
+
stream
|
|
671
|
+
);
|
|
672
|
+
bytes = stream.toByteArray();
|
|
673
|
+
|
|
674
|
+
// Write EXIF data back to resized image
|
|
675
|
+
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
676
|
+
} else {
|
|
677
|
+
// For non-resized images, ensure EXIF is saved
|
|
678
|
+
exifInterface.saveAttributes();
|
|
679
|
+
bytes = new byte[(int) tempFile.length()];
|
|
680
|
+
java.io.FileInputStream fis2 = new java.io.FileInputStream(
|
|
681
|
+
tempFile
|
|
682
|
+
);
|
|
683
|
+
fis2.read(bytes);
|
|
684
|
+
fis2.close();
|
|
685
|
+
}
|
|
374
686
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (listener != null) {
|
|
378
|
-
// Post the callback to ensure layout is complete
|
|
379
|
-
previewContainer.post(() -> {
|
|
380
|
-
// Return actual preview container dimensions instead of requested dimensions
|
|
381
|
-
int actualWidth = previewContainer != null ? previewContainer.getWidth() : sessionConfig.getWidth();
|
|
382
|
-
int actualHeight = previewContainer != null ? previewContainer.getHeight() : sessionConfig.getHeight();
|
|
383
|
-
int actualX = previewContainer != null ? previewContainer.getLeft() : sessionConfig.getX();
|
|
384
|
-
int actualY = previewContainer != null ? previewContainer.getTop() : sessionConfig.getY();
|
|
385
|
-
listener.onCameraStarted(actualWidth, actualHeight, actualX, actualY);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
} catch (Exception e) {
|
|
389
|
-
if (listener != null) listener.onCameraStartError("Error binding camera: " + e.getMessage());
|
|
687
|
+
if (saveToGallery) {
|
|
688
|
+
saveImageToGallery(bytes);
|
|
390
689
|
}
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
690
|
|
|
394
|
-
|
|
395
|
-
private CameraSelector buildCameraSelector() {
|
|
396
|
-
CameraSelector.Builder builder = new CameraSelector.Builder();
|
|
397
|
-
final String deviceId = sessionConfig.getDeviceId();
|
|
398
|
-
|
|
399
|
-
if (deviceId != null && !deviceId.isEmpty()) {
|
|
400
|
-
builder.addCameraFilter(cameraInfos -> {
|
|
401
|
-
for (CameraInfo cameraInfo : cameraInfos) {
|
|
402
|
-
if (deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
|
|
403
|
-
return Collections.singletonList(cameraInfo);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return Collections.emptyList();
|
|
407
|
-
});
|
|
408
|
-
} else {
|
|
409
|
-
String position = sessionConfig.getPosition();
|
|
410
|
-
int requiredFacing = "front".equals(position) ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK;
|
|
411
|
-
builder.requireLensFacing(requiredFacing);
|
|
412
|
-
}
|
|
413
|
-
return builder.build();
|
|
414
|
-
}
|
|
691
|
+
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
415
692
|
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
// Generate a stable ID based on camera characteristics
|
|
419
|
-
boolean isBack = isBackCamera(cameraInfo);
|
|
420
|
-
float minZoom = Objects.requireNonNull(cameraInfo.getZoomState().getValue()).getMinZoomRatio();
|
|
421
|
-
float maxZoom = cameraInfo.getZoomState().getValue().getMaxZoomRatio();
|
|
422
|
-
|
|
423
|
-
// Create a unique ID based on camera properties
|
|
424
|
-
String position = isBack ? "back" : "front";
|
|
425
|
-
return position + "_" + minZoom + "_" + maxZoom;
|
|
426
|
-
} catch (Exception e) {
|
|
427
|
-
return "unknown_camera";
|
|
428
|
-
}
|
|
429
|
-
}
|
|
693
|
+
tempFile.delete();
|
|
430
694
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
695
|
+
if (listener != null) {
|
|
696
|
+
listener.onPictureTaken(base64, exifData);
|
|
697
|
+
}
|
|
698
|
+
} catch (Exception e) {
|
|
699
|
+
Log.e(TAG, "capturePhoto: Error processing image", e);
|
|
700
|
+
if (listener != null) {
|
|
701
|
+
listener.onPictureTakenError(
|
|
702
|
+
"Error processing image: " + e.getMessage()
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private Bitmap resizeBitmap(Bitmap bitmap, int width, int height) {
|
|
712
|
+
return Bitmap.createScaledBitmap(bitmap, width, height, true);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private JSONObject getExifData(ExifInterface exifInterface) {
|
|
716
|
+
JSONObject exifData = new JSONObject();
|
|
717
|
+
try {
|
|
718
|
+
// Add all available exif tags to a JSON object
|
|
719
|
+
for (String[] tag : EXIF_TAGS) {
|
|
720
|
+
String value = exifInterface.getAttribute(tag[0]);
|
|
721
|
+
if (value != null) {
|
|
722
|
+
exifData.put(tag[1], value);
|
|
444
723
|
}
|
|
724
|
+
}
|
|
725
|
+
} catch (Exception e) {
|
|
726
|
+
Log.e(TAG, "getExifData: Error reading exif data", e);
|
|
445
727
|
}
|
|
728
|
+
return exifData;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private static final String[][] EXIF_TAGS = new String[][] {
|
|
732
|
+
{ ExifInterface.TAG_APERTURE_VALUE, "ApertureValue" },
|
|
733
|
+
{ ExifInterface.TAG_ARTIST, "Artist" },
|
|
734
|
+
{ ExifInterface.TAG_BITS_PER_SAMPLE, "BitsPerSample" },
|
|
735
|
+
{ ExifInterface.TAG_BRIGHTNESS_VALUE, "BrightnessValue" },
|
|
736
|
+
{ ExifInterface.TAG_CFA_PATTERN, "CFAPattern" },
|
|
737
|
+
{ ExifInterface.TAG_COLOR_SPACE, "ColorSpace" },
|
|
738
|
+
{ ExifInterface.TAG_COMPONENTS_CONFIGURATION, "ComponentsConfiguration" },
|
|
739
|
+
{ ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, "CompressedBitsPerPixel" },
|
|
740
|
+
{ ExifInterface.TAG_COMPRESSION, "Compression" },
|
|
741
|
+
{ ExifInterface.TAG_CONTRAST, "Contrast" },
|
|
742
|
+
{ ExifInterface.TAG_COPYRIGHT, "Copyright" },
|
|
743
|
+
{ ExifInterface.TAG_CUSTOM_RENDERED, "CustomRendered" },
|
|
744
|
+
{ ExifInterface.TAG_DATETIME, "DateTime" },
|
|
745
|
+
{ ExifInterface.TAG_DATETIME_DIGITIZED, "DateTimeDigitized" },
|
|
746
|
+
{ ExifInterface.TAG_DATETIME_ORIGINAL, "DateTimeOriginal" },
|
|
747
|
+
{
|
|
748
|
+
ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
|
|
749
|
+
"DeviceSettingDescription",
|
|
750
|
+
},
|
|
751
|
+
{ ExifInterface.TAG_DIGITAL_ZOOM_RATIO, "DigitalZoomRatio" },
|
|
752
|
+
{ ExifInterface.TAG_DNG_VERSION, "DNGVersion" },
|
|
753
|
+
{ ExifInterface.TAG_EXIF_VERSION, "ExifVersion" },
|
|
754
|
+
{ ExifInterface.TAG_EXPOSURE_BIAS_VALUE, "ExposureBiasValue" },
|
|
755
|
+
{ ExifInterface.TAG_EXPOSURE_INDEX, "ExposureIndex" },
|
|
756
|
+
{ ExifInterface.TAG_EXPOSURE_MODE, "ExposureMode" },
|
|
757
|
+
{ ExifInterface.TAG_EXPOSURE_PROGRAM, "ExposureProgram" },
|
|
758
|
+
{ ExifInterface.TAG_EXPOSURE_TIME, "ExposureTime" },
|
|
759
|
+
{ ExifInterface.TAG_FILE_SOURCE, "FileSource" },
|
|
760
|
+
{ ExifInterface.TAG_FLASH, "Flash" },
|
|
761
|
+
{ ExifInterface.TAG_FLASHPIX_VERSION, "FlashpixVersion" },
|
|
762
|
+
{ ExifInterface.TAG_FLASH_ENERGY, "FlashEnergy" },
|
|
763
|
+
{ ExifInterface.TAG_FOCAL_LENGTH, "FocalLength" },
|
|
764
|
+
{ ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, "FocalLengthIn35mmFilm" },
|
|
765
|
+
{
|
|
766
|
+
ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
|
|
767
|
+
"FocalPlaneResolutionUnit",
|
|
768
|
+
},
|
|
769
|
+
{ ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, "FocalPlaneXResolution" },
|
|
770
|
+
{ ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, "FocalPlaneYResolution" },
|
|
771
|
+
{ ExifInterface.TAG_F_NUMBER, "FNumber" },
|
|
772
|
+
{ ExifInterface.TAG_GAIN_CONTROL, "GainControl" },
|
|
773
|
+
{ ExifInterface.TAG_GPS_ALTITUDE, "GPSAltitude" },
|
|
774
|
+
{ ExifInterface.TAG_GPS_ALTITUDE_REF, "GPSAltitudeRef" },
|
|
775
|
+
{ ExifInterface.TAG_GPS_AREA_INFORMATION, "GPSAreaInformation" },
|
|
776
|
+
{ ExifInterface.TAG_GPS_DATESTAMP, "GPSDateStamp" },
|
|
777
|
+
{ ExifInterface.TAG_GPS_DEST_BEARING, "GPSDestBearing" },
|
|
778
|
+
{ ExifInterface.TAG_GPS_DEST_BEARING_REF, "GPSDestBearingRef" },
|
|
779
|
+
{ ExifInterface.TAG_GPS_DEST_DISTANCE, "GPSDestDistance" },
|
|
780
|
+
{ ExifInterface.TAG_GPS_DEST_DISTANCE_REF, "GPSDestDistanceRef" },
|
|
781
|
+
{ ExifInterface.TAG_GPS_DEST_LATITUDE, "GPSDestLatitude" },
|
|
782
|
+
{ ExifInterface.TAG_GPS_DEST_LATITUDE_REF, "GPSDestLatitudeRef" },
|
|
783
|
+
{ ExifInterface.TAG_GPS_DEST_LONGITUDE, "GPSDestLongitude" },
|
|
784
|
+
{ ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, "GPSDestLongitudeRef" },
|
|
785
|
+
{ ExifInterface.TAG_GPS_DIFFERENTIAL, "GPSDifferential" },
|
|
786
|
+
{ ExifInterface.TAG_GPS_DOP, "GPSDOP" },
|
|
787
|
+
{ ExifInterface.TAG_GPS_IMG_DIRECTION, "GPSImgDirection" },
|
|
788
|
+
{ ExifInterface.TAG_GPS_IMG_DIRECTION_REF, "GPSImgDirectionRef" },
|
|
789
|
+
{ ExifInterface.TAG_GPS_LATITUDE, "GPSLatitude" },
|
|
790
|
+
{ ExifInterface.TAG_GPS_LATITUDE_REF, "GPSLatitudeRef" },
|
|
791
|
+
{ ExifInterface.TAG_GPS_LONGITUDE, "GPSLongitude" },
|
|
792
|
+
{ ExifInterface.TAG_GPS_LONGITUDE_REF, "GPSLongitudeRef" },
|
|
793
|
+
{ ExifInterface.TAG_GPS_MAP_DATUM, "GPSMapDatum" },
|
|
794
|
+
{ ExifInterface.TAG_GPS_MEASURE_MODE, "GPSMeasureMode" },
|
|
795
|
+
{ ExifInterface.TAG_GPS_PROCESSING_METHOD, "GPSProcessingMethod" },
|
|
796
|
+
{ ExifInterface.TAG_GPS_SATELLITES, "GPSSatellites" },
|
|
797
|
+
{ ExifInterface.TAG_GPS_SPEED, "GPSSpeed" },
|
|
798
|
+
{ ExifInterface.TAG_GPS_SPEED_REF, "GPSSpeedRef" },
|
|
799
|
+
{ ExifInterface.TAG_GPS_STATUS, "GPSStatus" },
|
|
800
|
+
{ ExifInterface.TAG_GPS_TIMESTAMP, "GPSTimeStamp" },
|
|
801
|
+
{ ExifInterface.TAG_GPS_TRACK, "GPSTrack" },
|
|
802
|
+
{ ExifInterface.TAG_GPS_TRACK_REF, "GPSTrackRef" },
|
|
803
|
+
{ ExifInterface.TAG_GPS_VERSION_ID, "GPSVersionID" },
|
|
804
|
+
{ ExifInterface.TAG_IMAGE_DESCRIPTION, "ImageDescription" },
|
|
805
|
+
{ ExifInterface.TAG_IMAGE_LENGTH, "ImageLength" },
|
|
806
|
+
{ ExifInterface.TAG_IMAGE_UNIQUE_ID, "ImageUniqueID" },
|
|
807
|
+
{ ExifInterface.TAG_IMAGE_WIDTH, "ImageWidth" },
|
|
808
|
+
{ ExifInterface.TAG_INTEROPERABILITY_INDEX, "InteroperabilityIndex" },
|
|
809
|
+
{ ExifInterface.TAG_ISO_SPEED, "ISOSpeed" },
|
|
810
|
+
{ ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, "ISOSpeedLatitudeyyy" },
|
|
811
|
+
{ ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, "ISOSpeedLatitudezzz" },
|
|
812
|
+
{ ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, "JPEGInterchangeFormat" },
|
|
813
|
+
{
|
|
814
|
+
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
|
|
815
|
+
"JPEGInterchangeFormatLength",
|
|
816
|
+
},
|
|
817
|
+
{ ExifInterface.TAG_LIGHT_SOURCE, "LightSource" },
|
|
818
|
+
{ ExifInterface.TAG_MAKE, "Make" },
|
|
819
|
+
{ ExifInterface.TAG_MAKER_NOTE, "MakerNote" },
|
|
820
|
+
{ ExifInterface.TAG_MAX_APERTURE_VALUE, "MaxApertureValue" },
|
|
821
|
+
{ ExifInterface.TAG_METERING_MODE, "MeteringMode" },
|
|
822
|
+
{ ExifInterface.TAG_MODEL, "Model" },
|
|
823
|
+
{ ExifInterface.TAG_NEW_SUBFILE_TYPE, "NewSubfileType" },
|
|
824
|
+
{ ExifInterface.TAG_OECF, "OECF" },
|
|
825
|
+
{ ExifInterface.TAG_OFFSET_TIME, "OffsetTime" },
|
|
826
|
+
{ ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "OffsetTimeDigitized" },
|
|
827
|
+
{ ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "OffsetTimeOriginal" },
|
|
828
|
+
{ ExifInterface.TAG_ORF_ASPECT_FRAME, "ORFAspectFrame" },
|
|
829
|
+
{ ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH, "ORFPreviewImageLength" },
|
|
830
|
+
{ ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, "ORFPreviewImageStart" },
|
|
831
|
+
{ ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, "ORFThumbnailImage" },
|
|
832
|
+
{ ExifInterface.TAG_ORIENTATION, "Orientation" },
|
|
833
|
+
{
|
|
834
|
+
ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
|
|
835
|
+
"PhotometricInterpretation",
|
|
836
|
+
},
|
|
837
|
+
{ ExifInterface.TAG_PIXEL_X_DIMENSION, "PixelXDimension" },
|
|
838
|
+
{ ExifInterface.TAG_PIXEL_Y_DIMENSION, "PixelYDimension" },
|
|
839
|
+
{ ExifInterface.TAG_PLANAR_CONFIGURATION, "PlanarConfiguration" },
|
|
840
|
+
{ ExifInterface.TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities" },
|
|
841
|
+
{
|
|
842
|
+
ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
|
|
843
|
+
"RecommendedExposureIndex",
|
|
844
|
+
},
|
|
845
|
+
{ ExifInterface.TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite" },
|
|
846
|
+
{ ExifInterface.TAG_RELATED_SOUND_FILE, "RelatedSoundFile" },
|
|
847
|
+
{ ExifInterface.TAG_RESOLUTION_UNIT, "ResolutionUnit" },
|
|
848
|
+
{ ExifInterface.TAG_ROWS_PER_STRIP, "RowsPerStrip" },
|
|
849
|
+
{ ExifInterface.TAG_RW2_ISO, "RW2ISO" },
|
|
850
|
+
{ ExifInterface.TAG_RW2_JPG_FROM_RAW, "RW2JpgFromRaw" },
|
|
851
|
+
{ ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, "RW2SensorBottomBorder" },
|
|
852
|
+
{ ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, "RW2SensorLeftBorder" },
|
|
853
|
+
{ ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, "RW2SensorRightBorder" },
|
|
854
|
+
{ ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, "RW2SensorTopBorder" },
|
|
855
|
+
{ ExifInterface.TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel" },
|
|
856
|
+
{ ExifInterface.TAG_SATURATION, "Saturation" },
|
|
857
|
+
{ ExifInterface.TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType" },
|
|
858
|
+
{ ExifInterface.TAG_SCENE_TYPE, "SceneType" },
|
|
859
|
+
{ ExifInterface.TAG_SENSING_METHOD, "SensingMethod" },
|
|
860
|
+
{ ExifInterface.TAG_SENSITIVITY_TYPE, "SensitivityType" },
|
|
861
|
+
{ ExifInterface.TAG_SHARPNESS, "Sharpness" },
|
|
862
|
+
{ ExifInterface.TAG_SHUTTER_SPEED_VALUE, "ShutterSpeedValue" },
|
|
863
|
+
{ ExifInterface.TAG_SOFTWARE, "Software" },
|
|
864
|
+
{
|
|
865
|
+
ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
|
|
866
|
+
"SpatialFrequencyResponse",
|
|
867
|
+
},
|
|
868
|
+
{ ExifInterface.TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity" },
|
|
869
|
+
{
|
|
870
|
+
ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
|
|
871
|
+
"StandardOutputSensitivity",
|
|
872
|
+
},
|
|
873
|
+
{ ExifInterface.TAG_STRIP_BYTE_COUNTS, "StripByteCounts" },
|
|
874
|
+
{ ExifInterface.TAG_STRIP_OFFSETS, "StripOffsets" },
|
|
875
|
+
{ ExifInterface.TAG_SUBFILE_TYPE, "SubfileType" },
|
|
876
|
+
{ ExifInterface.TAG_SUBJECT_AREA, "SubjectArea" },
|
|
877
|
+
{ ExifInterface.TAG_SUBJECT_DISTANCE, "SubjectDistance" },
|
|
878
|
+
{ ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, "SubjectDistanceRange" },
|
|
879
|
+
{ ExifInterface.TAG_SUBJECT_LOCATION, "SubjectLocation" },
|
|
880
|
+
{ ExifInterface.TAG_SUBSEC_TIME, "SubSecTime" },
|
|
881
|
+
{ ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "SubSecTimeDigitized" },
|
|
882
|
+
{ ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, "SubSecTimeOriginal" },
|
|
883
|
+
{ ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, "ThumbnailImageLength" },
|
|
884
|
+
{ ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, "ThumbnailImageWidth" },
|
|
885
|
+
{ ExifInterface.TAG_TRANSFER_FUNCTION, "TransferFunction" },
|
|
886
|
+
{ ExifInterface.TAG_USER_COMMENT, "UserComment" },
|
|
887
|
+
{ ExifInterface.TAG_WHITE_BALANCE, "WhiteBalance" },
|
|
888
|
+
{ ExifInterface.TAG_WHITE_POINT, "WhitePoint" },
|
|
889
|
+
{ ExifInterface.TAG_X_RESOLUTION, "XResolution" },
|
|
890
|
+
{ ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients" },
|
|
891
|
+
{ ExifInterface.TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning" },
|
|
892
|
+
{ ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling" },
|
|
893
|
+
{ ExifInterface.TAG_Y_RESOLUTION, "YResolution" },
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
private byte[] writeExifToImageBytes(
|
|
897
|
+
byte[] imageBytes,
|
|
898
|
+
ExifInterface sourceExif
|
|
899
|
+
) {
|
|
900
|
+
try {
|
|
901
|
+
// Create a temporary file to write the image with EXIF
|
|
902
|
+
File tempExifFile = File.createTempFile(
|
|
903
|
+
"temp_exif",
|
|
904
|
+
".jpg",
|
|
905
|
+
context.getCacheDir()
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
// Write the image bytes to temp file
|
|
909
|
+
java.io.FileOutputStream fos = new java.io.FileOutputStream(tempExifFile);
|
|
910
|
+
fos.write(imageBytes);
|
|
911
|
+
fos.close();
|
|
912
|
+
|
|
913
|
+
// Create new ExifInterface for the temp file and copy all EXIF data
|
|
914
|
+
ExifInterface newExif = new ExifInterface(tempExifFile.getAbsolutePath());
|
|
915
|
+
|
|
916
|
+
// Copy all EXIF attributes from source to new
|
|
917
|
+
for (String[] tag : EXIF_TAGS) {
|
|
918
|
+
String value = sourceExif.getAttribute(tag[0]);
|
|
919
|
+
if (value != null) {
|
|
920
|
+
newExif.setAttribute(tag[0], value);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
446
923
|
|
|
447
|
-
|
|
448
|
-
|
|
924
|
+
// Save the EXIF data
|
|
925
|
+
newExif.saveAttributes();
|
|
449
926
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
927
|
+
// Read the file back with EXIF embedded
|
|
928
|
+
byte[] result = new byte[(int) tempExifFile.length()];
|
|
929
|
+
java.io.FileInputStream fis = new java.io.FileInputStream(tempExifFile);
|
|
930
|
+
fis.read(result);
|
|
931
|
+
fis.close();
|
|
456
932
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
imageCapture.takePicture(
|
|
461
|
-
outputFileOptions,
|
|
462
|
-
cameraExecutor,
|
|
463
|
-
new ImageCapture.OnImageSavedCallback() {
|
|
464
|
-
@Override
|
|
465
|
-
public void onError(@NonNull ImageCaptureException exception) {
|
|
466
|
-
Log.e(TAG, "capturePhoto: Photo capture failed", exception);
|
|
467
|
-
if (listener != null) {
|
|
468
|
-
listener.onPictureTakenError("Photo capture failed: " + exception.getMessage());
|
|
469
|
-
}
|
|
470
|
-
}
|
|
933
|
+
// Clean up temp file
|
|
934
|
+
tempExifFile.delete();
|
|
471
935
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
byte[] bytes = new byte[(int) tempFile.length()];
|
|
477
|
-
java.io.FileInputStream fis = new java.io.FileInputStream(tempFile);
|
|
478
|
-
fis.read(bytes);
|
|
479
|
-
fis.close();
|
|
480
|
-
|
|
481
|
-
ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
|
|
482
|
-
|
|
483
|
-
if (location != null) {
|
|
484
|
-
exifInterface.setGpsInfo(location);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
JSONObject exifData = getExifData(exifInterface);
|
|
488
|
-
|
|
489
|
-
if (width != null && height != null) {
|
|
490
|
-
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
491
|
-
Bitmap resizedBitmap = resizeBitmap(bitmap, width, height);
|
|
492
|
-
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
493
|
-
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
|
|
494
|
-
bytes = stream.toByteArray();
|
|
495
|
-
|
|
496
|
-
// Write EXIF data back to resized image
|
|
497
|
-
bytes = writeExifToImageBytes(bytes, exifInterface);
|
|
498
|
-
} else {
|
|
499
|
-
// For non-resized images, ensure EXIF is saved
|
|
500
|
-
exifInterface.saveAttributes();
|
|
501
|
-
bytes = new byte[(int) tempFile.length()];
|
|
502
|
-
java.io.FileInputStream fis2 = new java.io.FileInputStream(tempFile);
|
|
503
|
-
fis2.read(bytes);
|
|
504
|
-
fis2.close();
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (saveToGallery) {
|
|
508
|
-
saveImageToGallery(bytes);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
512
|
-
|
|
513
|
-
tempFile.delete();
|
|
514
|
-
|
|
515
|
-
if (listener != null) {
|
|
516
|
-
listener.onPictureTaken(base64, exifData);
|
|
517
|
-
}
|
|
518
|
-
} catch (Exception e) {
|
|
519
|
-
Log.e(TAG, "capturePhoto: Error processing image", e);
|
|
520
|
-
if (listener != null) {
|
|
521
|
-
listener.onPictureTakenError("Error processing image: " + e.getMessage());
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
);
|
|
936
|
+
return result;
|
|
937
|
+
} catch (Exception e) {
|
|
938
|
+
Log.e(TAG, "writeExifToImageBytes: Error writing EXIF data", e);
|
|
939
|
+
return imageBytes; // Return original bytes if error
|
|
527
940
|
}
|
|
941
|
+
}
|
|
528
942
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
943
|
+
public void captureSample(int quality) {
|
|
944
|
+
Log.d(
|
|
945
|
+
TAG,
|
|
946
|
+
"captureSample: Starting sample capture with quality: " + quality
|
|
947
|
+
);
|
|
532
948
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
String value = exifInterface.getAttribute(tag[0]);
|
|
539
|
-
if (value != null) {
|
|
540
|
-
exifData.put(tag[1], value);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
} catch (Exception e) {
|
|
544
|
-
Log.e(TAG, "getExifData: Error reading exif data", e);
|
|
545
|
-
}
|
|
546
|
-
return exifData;
|
|
949
|
+
if (sampleImageCapture == null) {
|
|
950
|
+
if (listener != null) {
|
|
951
|
+
listener.onSampleTakenError("Camera not ready");
|
|
952
|
+
}
|
|
953
|
+
return;
|
|
547
954
|
}
|
|
548
955
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
{
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
{ExifInterface.TAG_COPYRIGHT, "Copyright"},
|
|
561
|
-
{ExifInterface.TAG_CUSTOM_RENDERED, "CustomRendered"},
|
|
562
|
-
{ExifInterface.TAG_DATETIME, "DateTime"},
|
|
563
|
-
{ExifInterface.TAG_DATETIME_DIGITIZED, "DateTimeDigitized"},
|
|
564
|
-
{ExifInterface.TAG_DATETIME_ORIGINAL, "DateTimeOriginal"},
|
|
565
|
-
{ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, "DeviceSettingDescription"},
|
|
566
|
-
{ExifInterface.TAG_DIGITAL_ZOOM_RATIO, "DigitalZoomRatio"},
|
|
567
|
-
{ExifInterface.TAG_DNG_VERSION, "DNGVersion"},
|
|
568
|
-
{ExifInterface.TAG_EXIF_VERSION, "ExifVersion"},
|
|
569
|
-
{ExifInterface.TAG_EXPOSURE_BIAS_VALUE, "ExposureBiasValue"},
|
|
570
|
-
{ExifInterface.TAG_EXPOSURE_INDEX, "ExposureIndex"},
|
|
571
|
-
{ExifInterface.TAG_EXPOSURE_MODE, "ExposureMode"},
|
|
572
|
-
{ExifInterface.TAG_EXPOSURE_PROGRAM, "ExposureProgram"},
|
|
573
|
-
{ExifInterface.TAG_EXPOSURE_TIME, "ExposureTime"},
|
|
574
|
-
{ExifInterface.TAG_FILE_SOURCE, "FileSource"},
|
|
575
|
-
{ExifInterface.TAG_FLASH, "Flash"},
|
|
576
|
-
{ExifInterface.TAG_FLASHPIX_VERSION, "FlashpixVersion"},
|
|
577
|
-
{ExifInterface.TAG_FLASH_ENERGY, "FlashEnergy"},
|
|
578
|
-
{ExifInterface.TAG_FOCAL_LENGTH, "FocalLength"},
|
|
579
|
-
{ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, "FocalLengthIn35mmFilm"},
|
|
580
|
-
{ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, "FocalPlaneResolutionUnit"},
|
|
581
|
-
{ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, "FocalPlaneXResolution"},
|
|
582
|
-
{ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, "FocalPlaneYResolution"},
|
|
583
|
-
{ExifInterface.TAG_F_NUMBER, "FNumber"},
|
|
584
|
-
{ExifInterface.TAG_GAIN_CONTROL, "GainControl"},
|
|
585
|
-
{ExifInterface.TAG_GPS_ALTITUDE, "GPSAltitude"},
|
|
586
|
-
{ExifInterface.TAG_GPS_ALTITUDE_REF, "GPSAltitudeRef"},
|
|
587
|
-
{ExifInterface.TAG_GPS_AREA_INFORMATION, "GPSAreaInformation"},
|
|
588
|
-
{ExifInterface.TAG_GPS_DATESTAMP, "GPSDateStamp"},
|
|
589
|
-
{ExifInterface.TAG_GPS_DEST_BEARING, "GPSDestBearing"},
|
|
590
|
-
{ExifInterface.TAG_GPS_DEST_BEARING_REF, "GPSDestBearingRef"},
|
|
591
|
-
{ExifInterface.TAG_GPS_DEST_DISTANCE, "GPSDestDistance"},
|
|
592
|
-
{ExifInterface.TAG_GPS_DEST_DISTANCE_REF, "GPSDestDistanceRef"},
|
|
593
|
-
{ExifInterface.TAG_GPS_DEST_LATITUDE, "GPSDestLatitude"},
|
|
594
|
-
{ExifInterface.TAG_GPS_DEST_LATITUDE_REF, "GPSDestLatitudeRef"},
|
|
595
|
-
{ExifInterface.TAG_GPS_DEST_LONGITUDE, "GPSDestLongitude"},
|
|
596
|
-
{ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, "GPSDestLongitudeRef"},
|
|
597
|
-
{ExifInterface.TAG_GPS_DIFFERENTIAL, "GPSDifferential"},
|
|
598
|
-
{ExifInterface.TAG_GPS_DOP, "GPSDOP"},
|
|
599
|
-
{ExifInterface.TAG_GPS_IMG_DIRECTION, "GPSImgDirection"},
|
|
600
|
-
{ExifInterface.TAG_GPS_IMG_DIRECTION_REF, "GPSImgDirectionRef"},
|
|
601
|
-
{ExifInterface.TAG_GPS_LATITUDE, "GPSLatitude"},
|
|
602
|
-
{ExifInterface.TAG_GPS_LATITUDE_REF, "GPSLatitudeRef"},
|
|
603
|
-
{ExifInterface.TAG_GPS_LONGITUDE, "GPSLongitude"},
|
|
604
|
-
{ExifInterface.TAG_GPS_LONGITUDE_REF, "GPSLongitudeRef"},
|
|
605
|
-
{ExifInterface.TAG_GPS_MAP_DATUM, "GPSMapDatum"},
|
|
606
|
-
{ExifInterface.TAG_GPS_MEASURE_MODE, "GPSMeasureMode"},
|
|
607
|
-
{ExifInterface.TAG_GPS_PROCESSING_METHOD, "GPSProcessingMethod"},
|
|
608
|
-
{ExifInterface.TAG_GPS_SATELLITES, "GPSSatellites"},
|
|
609
|
-
{ExifInterface.TAG_GPS_SPEED, "GPSSpeed"},
|
|
610
|
-
{ExifInterface.TAG_GPS_SPEED_REF, "GPSSpeedRef"},
|
|
611
|
-
{ExifInterface.TAG_GPS_STATUS, "GPSStatus"},
|
|
612
|
-
{ExifInterface.TAG_GPS_TIMESTAMP, "GPSTimeStamp"},
|
|
613
|
-
{ExifInterface.TAG_GPS_TRACK, "GPSTrack"},
|
|
614
|
-
{ExifInterface.TAG_GPS_TRACK_REF, "GPSTrackRef"},
|
|
615
|
-
{ExifInterface.TAG_GPS_VERSION_ID, "GPSVersionID"},
|
|
616
|
-
{ExifInterface.TAG_IMAGE_DESCRIPTION, "ImageDescription"},
|
|
617
|
-
{ExifInterface.TAG_IMAGE_LENGTH, "ImageLength"},
|
|
618
|
-
{ExifInterface.TAG_IMAGE_UNIQUE_ID, "ImageUniqueID"},
|
|
619
|
-
{ExifInterface.TAG_IMAGE_WIDTH, "ImageWidth"},
|
|
620
|
-
{ExifInterface.TAG_INTEROPERABILITY_INDEX, "InteroperabilityIndex"},
|
|
621
|
-
{ExifInterface.TAG_ISO_SPEED, "ISOSpeed"},
|
|
622
|
-
{ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, "ISOSpeedLatitudeyyy"},
|
|
623
|
-
{ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, "ISOSpeedLatitudezzz"},
|
|
624
|
-
{ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, "JPEGInterchangeFormat"},
|
|
625
|
-
{ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, "JPEGInterchangeFormatLength"},
|
|
626
|
-
{ExifInterface.TAG_LIGHT_SOURCE, "LightSource"},
|
|
627
|
-
{ExifInterface.TAG_MAKE, "Make"},
|
|
628
|
-
{ExifInterface.TAG_MAKER_NOTE, "MakerNote"},
|
|
629
|
-
{ExifInterface.TAG_MAX_APERTURE_VALUE, "MaxApertureValue"},
|
|
630
|
-
{ExifInterface.TAG_METERING_MODE, "MeteringMode"},
|
|
631
|
-
{ExifInterface.TAG_MODEL, "Model"},
|
|
632
|
-
{ExifInterface.TAG_NEW_SUBFILE_TYPE, "NewSubfileType"},
|
|
633
|
-
{ExifInterface.TAG_OECF, "OECF"},
|
|
634
|
-
{ExifInterface.TAG_OFFSET_TIME, "OffsetTime"},
|
|
635
|
-
{ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "OffsetTimeDigitized"},
|
|
636
|
-
{ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "OffsetTimeOriginal"},
|
|
637
|
-
{ExifInterface.TAG_ORF_ASPECT_FRAME, "ORFAspectFrame"},
|
|
638
|
-
{ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH, "ORFPreviewImageLength"},
|
|
639
|
-
{ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, "ORFPreviewImageStart"},
|
|
640
|
-
{ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, "ORFThumbnailImage"},
|
|
641
|
-
{ExifInterface.TAG_ORIENTATION, "Orientation"},
|
|
642
|
-
{ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation"},
|
|
643
|
-
{ExifInterface.TAG_PIXEL_X_DIMENSION, "PixelXDimension"},
|
|
644
|
-
{ExifInterface.TAG_PIXEL_Y_DIMENSION, "PixelYDimension"},
|
|
645
|
-
{ExifInterface.TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"},
|
|
646
|
-
{ExifInterface.TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"},
|
|
647
|
-
{ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, "RecommendedExposureIndex"},
|
|
648
|
-
{ExifInterface.TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"},
|
|
649
|
-
{ExifInterface.TAG_RELATED_SOUND_FILE, "RelatedSoundFile"},
|
|
650
|
-
{ExifInterface.TAG_RESOLUTION_UNIT, "ResolutionUnit"},
|
|
651
|
-
{ExifInterface.TAG_ROWS_PER_STRIP, "RowsPerStrip"},
|
|
652
|
-
{ExifInterface.TAG_RW2_ISO, "RW2ISO"},
|
|
653
|
-
{ExifInterface.TAG_RW2_JPG_FROM_RAW, "RW2JpgFromRaw"},
|
|
654
|
-
{ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, "RW2SensorBottomBorder"},
|
|
655
|
-
{ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, "RW2SensorLeftBorder"},
|
|
656
|
-
{ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, "RW2SensorRightBorder"},
|
|
657
|
-
{ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, "RW2SensorTopBorder"},
|
|
658
|
-
{ExifInterface.TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"},
|
|
659
|
-
{ExifInterface.TAG_SATURATION, "Saturation"},
|
|
660
|
-
{ExifInterface.TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"},
|
|
661
|
-
{ExifInterface.TAG_SCENE_TYPE, "SceneType"},
|
|
662
|
-
{ExifInterface.TAG_SENSING_METHOD, "SensingMethod"},
|
|
663
|
-
{ExifInterface.TAG_SENSITIVITY_TYPE, "SensitivityType"},
|
|
664
|
-
{ExifInterface.TAG_SHARPNESS, "Sharpness"},
|
|
665
|
-
{ExifInterface.TAG_SHUTTER_SPEED_VALUE, "ShutterSpeedValue"},
|
|
666
|
-
{ExifInterface.TAG_SOFTWARE, "Software"},
|
|
667
|
-
{ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, "SpatialFrequencyResponse"},
|
|
668
|
-
{ExifInterface.TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"},
|
|
669
|
-
{ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, "StandardOutputSensitivity"},
|
|
670
|
-
{ExifInterface.TAG_STRIP_BYTE_COUNTS, "StripByteCounts"},
|
|
671
|
-
{ExifInterface.TAG_STRIP_OFFSETS, "StripOffsets"},
|
|
672
|
-
{ExifInterface.TAG_SUBFILE_TYPE, "SubfileType"},
|
|
673
|
-
{ExifInterface.TAG_SUBJECT_AREA, "SubjectArea"},
|
|
674
|
-
{ExifInterface.TAG_SUBJECT_DISTANCE, "SubjectDistance"},
|
|
675
|
-
{ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, "SubjectDistanceRange"},
|
|
676
|
-
{ExifInterface.TAG_SUBJECT_LOCATION, "SubjectLocation"},
|
|
677
|
-
{ExifInterface.TAG_SUBSEC_TIME, "SubSecTime"},
|
|
678
|
-
{ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "SubSecTimeDigitized"},
|
|
679
|
-
{ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, "SubSecTimeOriginal"},
|
|
680
|
-
{ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, "ThumbnailImageLength"},
|
|
681
|
-
{ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, "ThumbnailImageWidth"},
|
|
682
|
-
{ExifInterface.TAG_TRANSFER_FUNCTION, "TransferFunction"},
|
|
683
|
-
{ExifInterface.TAG_USER_COMMENT, "UserComment"},
|
|
684
|
-
{ExifInterface.TAG_WHITE_BALANCE, "WhiteBalance"},
|
|
685
|
-
{ExifInterface.TAG_WHITE_POINT, "WhitePoint"},
|
|
686
|
-
{ExifInterface.TAG_X_RESOLUTION, "XResolution"},
|
|
687
|
-
{ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"},
|
|
688
|
-
{ExifInterface.TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"},
|
|
689
|
-
{ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"},
|
|
690
|
-
{ExifInterface.TAG_Y_RESOLUTION, "YResolution"}
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
private byte[] writeExifToImageBytes(byte[] imageBytes, ExifInterface sourceExif) {
|
|
694
|
-
try {
|
|
695
|
-
// Create a temporary file to write the image with EXIF
|
|
696
|
-
File tempExifFile = File.createTempFile("temp_exif", ".jpg", context.getCacheDir());
|
|
697
|
-
|
|
698
|
-
// Write the image bytes to temp file
|
|
699
|
-
java.io.FileOutputStream fos = new java.io.FileOutputStream(tempExifFile);
|
|
700
|
-
fos.write(imageBytes);
|
|
701
|
-
fos.close();
|
|
702
|
-
|
|
703
|
-
// Create new ExifInterface for the temp file and copy all EXIF data
|
|
704
|
-
ExifInterface newExif = new ExifInterface(tempExifFile.getAbsolutePath());
|
|
705
|
-
|
|
706
|
-
// Copy all EXIF attributes from source to new
|
|
707
|
-
for (String[] tag : EXIF_TAGS) {
|
|
708
|
-
String value = sourceExif.getAttribute(tag[0]);
|
|
709
|
-
if (value != null) {
|
|
710
|
-
newExif.setAttribute(tag[0], value);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Save the EXIF data
|
|
715
|
-
newExif.saveAttributes();
|
|
716
|
-
|
|
717
|
-
// Read the file back with EXIF embedded
|
|
718
|
-
byte[] result = new byte[(int) tempExifFile.length()];
|
|
719
|
-
java.io.FileInputStream fis = new java.io.FileInputStream(tempExifFile);
|
|
720
|
-
fis.read(result);
|
|
721
|
-
fis.close();
|
|
722
|
-
|
|
723
|
-
// Clean up temp file
|
|
724
|
-
tempExifFile.delete();
|
|
725
|
-
|
|
726
|
-
return result;
|
|
727
|
-
} catch (Exception e) {
|
|
728
|
-
Log.e(TAG, "writeExifToImageBytes: Error writing EXIF data", e);
|
|
729
|
-
return imageBytes; // Return original bytes if error
|
|
956
|
+
sampleImageCapture.takePicture(
|
|
957
|
+
cameraExecutor,
|
|
958
|
+
new ImageCapture.OnImageCapturedCallback() {
|
|
959
|
+
@Override
|
|
960
|
+
public void onError(@NonNull ImageCaptureException exception) {
|
|
961
|
+
Log.e(TAG, "captureSample: Sample capture failed", exception);
|
|
962
|
+
if (listener != null) {
|
|
963
|
+
listener.onSampleTakenError(
|
|
964
|
+
"Sample capture failed: " + exception.getMessage()
|
|
965
|
+
);
|
|
966
|
+
}
|
|
730
967
|
}
|
|
731
|
-
}
|
|
732
968
|
|
|
733
|
-
|
|
734
|
-
|
|
969
|
+
@Override
|
|
970
|
+
public void onCaptureSuccess(@NonNull ImageProxy image) {
|
|
971
|
+
try {
|
|
972
|
+
// Convert ImageProxy to byte array
|
|
973
|
+
byte[] bytes = imageProxyToByteArray(image);
|
|
974
|
+
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
735
975
|
|
|
736
|
-
if (sampleImageCapture == null) {
|
|
737
976
|
if (listener != null) {
|
|
738
|
-
|
|
977
|
+
listener.onSampleTaken(base64);
|
|
739
978
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
@Override
|
|
747
|
-
public void onError(@NonNull ImageCaptureException exception) {
|
|
748
|
-
Log.e(TAG, "captureSample: Sample capture failed", exception);
|
|
749
|
-
if (listener != null) {
|
|
750
|
-
listener.onSampleTakenError("Sample capture failed: " + exception.getMessage());
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
@Override
|
|
755
|
-
public void onCaptureSuccess(@NonNull ImageProxy image) {
|
|
756
|
-
try {
|
|
757
|
-
// Convert ImageProxy to byte array
|
|
758
|
-
byte[] bytes = imageProxyToByteArray(image);
|
|
759
|
-
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
760
|
-
|
|
761
|
-
if (listener != null) {
|
|
762
|
-
listener.onSampleTaken(base64);
|
|
763
|
-
}
|
|
764
|
-
} catch (Exception e) {
|
|
765
|
-
Log.e(TAG, "captureSample: Error processing sample", e);
|
|
766
|
-
if (listener != null) {
|
|
767
|
-
listener.onSampleTakenError("Error processing sample: " + e.getMessage());
|
|
768
|
-
}
|
|
769
|
-
} finally {
|
|
770
|
-
image.close();
|
|
771
|
-
}
|
|
772
|
-
}
|
|
979
|
+
} catch (Exception e) {
|
|
980
|
+
Log.e(TAG, "captureSample: Error processing sample", e);
|
|
981
|
+
if (listener != null) {
|
|
982
|
+
listener.onSampleTakenError(
|
|
983
|
+
"Error processing sample: " + e.getMessage()
|
|
984
|
+
);
|
|
773
985
|
}
|
|
986
|
+
} finally {
|
|
987
|
+
image.close();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
private byte[] imageProxyToByteArray(ImageProxy image) {
|
|
995
|
+
ImageProxy.PlaneProxy[] planes = image.getPlanes();
|
|
996
|
+
ByteBuffer buffer = planes[0].getBuffer();
|
|
997
|
+
byte[] bytes = new byte[buffer.remaining()];
|
|
998
|
+
buffer.get(bytes);
|
|
999
|
+
return bytes;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// not workin for xiaomi https://xiaomi.eu/community/threads/mi-11-ultra-unable-to-access-camera-lenses-in-apps-camera2-api.61456/
|
|
1003
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1004
|
+
public static List<
|
|
1005
|
+
com.ahm.capacitor.camera.preview.model.CameraDevice
|
|
1006
|
+
> getAvailableDevicesStatic(Context context) {
|
|
1007
|
+
Log.d(
|
|
1008
|
+
TAG,
|
|
1009
|
+
"getAvailableDevicesStatic: Starting CameraX device enumeration with getPhysicalCameraInfos."
|
|
1010
|
+
);
|
|
1011
|
+
List<com.ahm.capacitor.camera.preview.model.CameraDevice> devices =
|
|
1012
|
+
new ArrayList<>();
|
|
1013
|
+
try {
|
|
1014
|
+
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
|
|
1015
|
+
ProcessCameraProvider.getInstance(context);
|
|
1016
|
+
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
|
|
1017
|
+
CameraManager cameraManager = (CameraManager) context.getSystemService(
|
|
1018
|
+
Context.CAMERA_SERVICE
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
for (CameraInfo cameraInfo : cameraProvider.getAvailableCameraInfos()) {
|
|
1022
|
+
String logicalCameraId = Camera2CameraInfo.from(
|
|
1023
|
+
cameraInfo
|
|
1024
|
+
).getCameraId();
|
|
1025
|
+
String position = isBackCamera(cameraInfo) ? "rear" : "front";
|
|
1026
|
+
|
|
1027
|
+
// Add logical camera
|
|
1028
|
+
float minZoom = Objects.requireNonNull(
|
|
1029
|
+
cameraInfo.getZoomState().getValue()
|
|
1030
|
+
).getMinZoomRatio();
|
|
1031
|
+
float maxZoom = cameraInfo.getZoomState().getValue().getMaxZoomRatio();
|
|
1032
|
+
List<LensInfo> logicalLenses = new ArrayList<>();
|
|
1033
|
+
logicalLenses.add(new LensInfo(4.25f, "wideAngle", 1.0f, maxZoom));
|
|
1034
|
+
devices.add(
|
|
1035
|
+
new com.ahm.capacitor.camera.preview.model.CameraDevice(
|
|
1036
|
+
logicalCameraId,
|
|
1037
|
+
"Logical Camera (" + position + ")",
|
|
1038
|
+
position,
|
|
1039
|
+
logicalLenses,
|
|
1040
|
+
minZoom,
|
|
1041
|
+
maxZoom,
|
|
1042
|
+
true
|
|
1043
|
+
)
|
|
1044
|
+
);
|
|
1045
|
+
Log.d(
|
|
1046
|
+
TAG,
|
|
1047
|
+
"Found logical camera: " +
|
|
1048
|
+
logicalCameraId +
|
|
1049
|
+
" (" +
|
|
1050
|
+
position +
|
|
1051
|
+
") with zoom " +
|
|
1052
|
+
minZoom +
|
|
1053
|
+
"-" +
|
|
1054
|
+
maxZoom
|
|
774
1055
|
);
|
|
775
|
-
}
|
|
776
1056
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1057
|
+
// Get and add physical cameras
|
|
1058
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
1059
|
+
Set<CameraInfo> physicalCameraInfos =
|
|
1060
|
+
cameraInfo.getPhysicalCameraInfos();
|
|
1061
|
+
if (physicalCameraInfos.isEmpty()) continue;
|
|
1062
|
+
|
|
1063
|
+
Log.d(
|
|
1064
|
+
TAG,
|
|
1065
|
+
"Logical camera " +
|
|
1066
|
+
logicalCameraId +
|
|
1067
|
+
" has " +
|
|
1068
|
+
physicalCameraInfos.size() +
|
|
1069
|
+
" physical cameras."
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
for (CameraInfo physicalCameraInfo : physicalCameraInfos) {
|
|
1073
|
+
String physicalId = Camera2CameraInfo.from(
|
|
1074
|
+
physicalCameraInfo
|
|
1075
|
+
).getCameraId();
|
|
1076
|
+
if (physicalId.equals(logicalCameraId)) continue; // Already added as logical
|
|
784
1077
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (focalLengths != null && focalLengths.length > 0 && sensorSize != null && sensorSize.getWidth() > 0) {
|
|
827
|
-
double fov = 2 * Math.toDegrees(Math.atan(sensorSize.getWidth() / (2 * focalLengths[0])));
|
|
828
|
-
if (fov > 90) deviceType = "ultraWide";
|
|
829
|
-
else if (fov < 40) deviceType = "telephoto";
|
|
830
|
-
} else if (focalLengths != null && focalLengths.length > 0) {
|
|
831
|
-
if (focalLengths[0] < 3.0f) deviceType = "ultraWide";
|
|
832
|
-
else if (focalLengths[0] > 5.0f) deviceType = "telephoto";
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
float physicalMinZoom = 1.0f;
|
|
836
|
-
float physicalMaxZoom = 1.0f;
|
|
837
|
-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
|
838
|
-
android.util.Range<Float> zoomRange = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
|
|
839
|
-
if (zoomRange != null) {
|
|
840
|
-
physicalMinZoom = zoomRange.getLower();
|
|
841
|
-
physicalMaxZoom = zoomRange.getUpper();
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
String label = "Physical " + deviceType + " (" + position + ")";
|
|
846
|
-
List<LensInfo> physicalLenses = new ArrayList<>();
|
|
847
|
-
physicalLenses.add(new LensInfo(focalLengths != null ? focalLengths[0] : 4.25f, deviceType, 1.0f, physicalMaxZoom));
|
|
848
|
-
|
|
849
|
-
devices.add(new com.ahm.capacitor.camera.preview.model.CameraDevice(
|
|
850
|
-
physicalId, label, position, physicalLenses, physicalMinZoom, physicalMaxZoom, false
|
|
851
|
-
));
|
|
852
|
-
Log.d(TAG, "Found physical camera: " + physicalId + " (" + label + ")");
|
|
853
|
-
} catch (CameraAccessException e) {
|
|
854
|
-
Log.e(TAG, "Failed to access characteristics for physical camera " + physicalId, e);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
1078
|
+
try {
|
|
1079
|
+
CameraCharacteristics characteristics =
|
|
1080
|
+
cameraManager.getCameraCharacteristics(physicalId);
|
|
1081
|
+
String deviceType = "wideAngle";
|
|
1082
|
+
float[] focalLengths = characteristics.get(
|
|
1083
|
+
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
|
|
1084
|
+
);
|
|
1085
|
+
android.util.SizeF sensorSize = characteristics.get(
|
|
1086
|
+
CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
if (
|
|
1090
|
+
focalLengths != null &&
|
|
1091
|
+
focalLengths.length > 0 &&
|
|
1092
|
+
sensorSize != null &&
|
|
1093
|
+
sensorSize.getWidth() > 0
|
|
1094
|
+
) {
|
|
1095
|
+
double fov =
|
|
1096
|
+
2 *
|
|
1097
|
+
Math.toDegrees(
|
|
1098
|
+
Math.atan(sensorSize.getWidth() / (2 * focalLengths[0]))
|
|
1099
|
+
);
|
|
1100
|
+
if (fov > 90) deviceType = "ultraWide";
|
|
1101
|
+
else if (fov < 40) deviceType = "telephoto";
|
|
1102
|
+
} else if (focalLengths != null && focalLengths.length > 0) {
|
|
1103
|
+
if (focalLengths[0] < 3.0f) deviceType = "ultraWide";
|
|
1104
|
+
else if (focalLengths[0] > 5.0f) deviceType = "telephoto";
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
float physicalMinZoom = 1.0f;
|
|
1108
|
+
float physicalMaxZoom = 1.0f;
|
|
1109
|
+
if (
|
|
1110
|
+
android.os.Build.VERSION.SDK_INT >=
|
|
1111
|
+
android.os.Build.VERSION_CODES.R
|
|
1112
|
+
) {
|
|
1113
|
+
android.util.Range<Float> zoomRange = characteristics.get(
|
|
1114
|
+
CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE
|
|
1115
|
+
);
|
|
1116
|
+
if (zoomRange != null) {
|
|
1117
|
+
physicalMinZoom = zoomRange.getLower();
|
|
1118
|
+
physicalMaxZoom = zoomRange.getUpper();
|
|
857
1119
|
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
String label = "Physical " + deviceType + " (" + position + ")";
|
|
1123
|
+
List<LensInfo> physicalLenses = new ArrayList<>();
|
|
1124
|
+
physicalLenses.add(
|
|
1125
|
+
new LensInfo(
|
|
1126
|
+
focalLengths != null ? focalLengths[0] : 4.25f,
|
|
1127
|
+
deviceType,
|
|
1128
|
+
1.0f,
|
|
1129
|
+
physicalMaxZoom
|
|
1130
|
+
)
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
devices.add(
|
|
1134
|
+
new com.ahm.capacitor.camera.preview.model.CameraDevice(
|
|
1135
|
+
physicalId,
|
|
1136
|
+
label,
|
|
1137
|
+
position,
|
|
1138
|
+
physicalLenses,
|
|
1139
|
+
physicalMinZoom,
|
|
1140
|
+
physicalMaxZoom,
|
|
1141
|
+
false
|
|
1142
|
+
)
|
|
1143
|
+
);
|
|
1144
|
+
Log.d(
|
|
1145
|
+
TAG,
|
|
1146
|
+
"Found physical camera: " + physicalId + " (" + label + ")"
|
|
1147
|
+
);
|
|
1148
|
+
} catch (CameraAccessException e) {
|
|
1149
|
+
Log.e(
|
|
1150
|
+
TAG,
|
|
1151
|
+
"Failed to access characteristics for physical camera " +
|
|
1152
|
+
physicalId,
|
|
1153
|
+
e
|
|
1154
|
+
);
|
|
858
1155
|
}
|
|
859
|
-
|
|
860
|
-
} catch (Exception e) {
|
|
861
|
-
Log.e(TAG, "getAvailableDevicesStatic: Error getting devices", e);
|
|
862
|
-
return Collections.emptyList();
|
|
1156
|
+
}
|
|
863
1157
|
}
|
|
1158
|
+
}
|
|
1159
|
+
return devices;
|
|
1160
|
+
} catch (Exception e) {
|
|
1161
|
+
Log.e(TAG, "getAvailableDevicesStatic: Error getting devices", e);
|
|
1162
|
+
return Collections.emptyList();
|
|
864
1163
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
public static ZoomFactors getZoomFactorsStatic() {
|
|
1167
|
+
try {
|
|
1168
|
+
// For static method, return default zoom factors
|
|
1169
|
+
// We can try to detect if ultra-wide is available by checking device list
|
|
1170
|
+
|
|
1171
|
+
float minZoom = 1.0f;
|
|
1172
|
+
float maxZoom = 10.0f;
|
|
1173
|
+
|
|
1174
|
+
Log.d(
|
|
1175
|
+
TAG,
|
|
1176
|
+
"getZoomFactorsStatic: Final range - minZoom: " +
|
|
1177
|
+
minZoom +
|
|
1178
|
+
", maxZoom: " +
|
|
1179
|
+
maxZoom
|
|
1180
|
+
);
|
|
1181
|
+
LensInfo defaultLens = new LensInfo(4.25f, "wideAngle", 1.0f, 1.0f);
|
|
1182
|
+
return new ZoomFactors(minZoom, maxZoom, 1.0f, defaultLens);
|
|
1183
|
+
} catch (Exception e) {
|
|
1184
|
+
Log.e(TAG, "getZoomFactorsStatic: Error getting zoom factors", e);
|
|
1185
|
+
LensInfo defaultLens = new LensInfo(4.25f, "wideAngle", 1.0f, 1.0f);
|
|
1186
|
+
return new ZoomFactors(1.0f, 10.0f, 1.0f, defaultLens);
|
|
882
1187
|
}
|
|
1188
|
+
}
|
|
883
1189
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
try {
|
|
890
|
-
// Get the current zoom from active camera
|
|
891
|
-
float currentZoom = Objects.requireNonNull(camera.getCameraInfo().getZoomState().getValue()).getZoomRatio();
|
|
892
|
-
float minZoom = camera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
|
|
893
|
-
float maxZoom = camera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
|
|
894
|
-
|
|
895
|
-
Log.d(TAG, "getZoomFactors: Combined range - minZoom: " + minZoom + ", maxZoom: " + maxZoom + ", currentZoom: " + currentZoom);
|
|
896
|
-
|
|
897
|
-
return new ZoomFactors(minZoom, maxZoom, currentZoom, getCurrentLensInfo());
|
|
898
|
-
} catch (Exception e) {
|
|
899
|
-
Log.e(TAG, "getZoomFactors: Error getting zoom factors", e);
|
|
900
|
-
return new ZoomFactors(1.0f, 1.0f, 1.0f, getCurrentLensInfo());
|
|
901
|
-
}
|
|
1190
|
+
public ZoomFactors getZoomFactors() {
|
|
1191
|
+
if (camera == null) {
|
|
1192
|
+
return getZoomFactorsStatic();
|
|
902
1193
|
}
|
|
903
1194
|
|
|
1195
|
+
try {
|
|
1196
|
+
// Get the current zoom from active camera
|
|
1197
|
+
float currentZoom = Objects.requireNonNull(
|
|
1198
|
+
camera.getCameraInfo().getZoomState().getValue()
|
|
1199
|
+
).getZoomRatio();
|
|
1200
|
+
float minZoom = camera
|
|
1201
|
+
.getCameraInfo()
|
|
1202
|
+
.getZoomState()
|
|
1203
|
+
.getValue()
|
|
1204
|
+
.getMinZoomRatio();
|
|
1205
|
+
float maxZoom = camera
|
|
1206
|
+
.getCameraInfo()
|
|
1207
|
+
.getZoomState()
|
|
1208
|
+
.getValue()
|
|
1209
|
+
.getMaxZoomRatio();
|
|
1210
|
+
|
|
1211
|
+
Log.d(
|
|
1212
|
+
TAG,
|
|
1213
|
+
"getZoomFactors: Combined range - minZoom: " +
|
|
1214
|
+
minZoom +
|
|
1215
|
+
", maxZoom: " +
|
|
1216
|
+
maxZoom +
|
|
1217
|
+
", currentZoom: " +
|
|
1218
|
+
currentZoom
|
|
1219
|
+
);
|
|
1220
|
+
|
|
1221
|
+
return new ZoomFactors(
|
|
1222
|
+
minZoom,
|
|
1223
|
+
maxZoom,
|
|
1224
|
+
currentZoom,
|
|
1225
|
+
getCurrentLensInfo()
|
|
1226
|
+
);
|
|
1227
|
+
} catch (Exception e) {
|
|
1228
|
+
Log.e(TAG, "getZoomFactors: Error getting zoom factors", e);
|
|
1229
|
+
return new ZoomFactors(1.0f, 1.0f, 1.0f, getCurrentLensInfo());
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
904
1232
|
|
|
1233
|
+
private LensInfo getCurrentLensInfo() {
|
|
1234
|
+
if (camera == null) {
|
|
1235
|
+
return new LensInfo(4.25f, "wideAngle", 1.0f, 1.0f);
|
|
1236
|
+
}
|
|
905
1237
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
try {
|
|
912
|
-
float currentZoom = Objects.requireNonNull(camera.getCameraInfo().getZoomState().getValue()).getZoomRatio();
|
|
1238
|
+
try {
|
|
1239
|
+
float currentZoom = Objects.requireNonNull(
|
|
1240
|
+
camera.getCameraInfo().getZoomState().getValue()
|
|
1241
|
+
).getZoomRatio();
|
|
913
1242
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1243
|
+
// Determine device type based on zoom capabilities
|
|
1244
|
+
String deviceType = "wideAngle";
|
|
1245
|
+
float baseZoomRatio = 1.0f;
|
|
917
1246
|
|
|
918
|
-
|
|
1247
|
+
float digitalZoom = currentZoom / baseZoomRatio;
|
|
919
1248
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
1249
|
+
return new LensInfo(4.25f, deviceType, baseZoomRatio, digitalZoom);
|
|
1250
|
+
} catch (Exception e) {
|
|
1251
|
+
Log.e(TAG, "getCurrentLensInfo: Error getting lens info", e);
|
|
1252
|
+
return new LensInfo(4.25f, "wideAngle", 1.0f, 1.0f);
|
|
925
1253
|
}
|
|
1254
|
+
}
|
|
926
1255
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
Log.d(TAG, "setZoom: Requested zoom ratio: " + zoomRatio);
|
|
933
|
-
|
|
934
|
-
// Just let CameraX handle everything - it should automatically switch lenses
|
|
935
|
-
try {
|
|
936
|
-
ListenableFuture<Void> zoomFuture = camera.getCameraControl().setZoomRatio(zoomRatio);
|
|
937
|
-
|
|
938
|
-
// Add callback to see what actually happened
|
|
939
|
-
zoomFuture.addListener(() -> {
|
|
940
|
-
try {
|
|
941
|
-
float actualZoom = Objects.requireNonNull(camera.getCameraInfo().getZoomState().getValue()).getZoomRatio();
|
|
942
|
-
Log.d(TAG, "setZoom: CameraX set zoom to " + actualZoom + " (requested: " + zoomRatio + ")");
|
|
943
|
-
if (Math.abs(actualZoom - zoomRatio) > 0.1f) {
|
|
944
|
-
Log.w(TAG, "setZoom: CameraX clamped zoom from " + zoomRatio + " to " + actualZoom);
|
|
945
|
-
} else {
|
|
946
|
-
Log.d(TAG, "setZoom: CameraX successfully set requested zoom");
|
|
947
|
-
}
|
|
948
|
-
} catch (Exception e) {
|
|
949
|
-
Log.e(TAG, "setZoom: Error checking final zoom", e);
|
|
950
|
-
}
|
|
951
|
-
}, mainExecutor);
|
|
952
|
-
|
|
953
|
-
} catch (Exception e) {
|
|
954
|
-
Log.e(TAG, "setZoom: Failed to set zoom to " + zoomRatio, e);
|
|
955
|
-
throw e;
|
|
956
|
-
}
|
|
1256
|
+
public void setZoom(float zoomRatio) throws Exception {
|
|
1257
|
+
if (camera == null) {
|
|
1258
|
+
throw new Exception("Camera not initialized");
|
|
957
1259
|
}
|
|
958
1260
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1261
|
+
Log.d(TAG, "setZoom: Requested zoom ratio: " + zoomRatio);
|
|
1262
|
+
|
|
1263
|
+
// Just let CameraX handle everything - it should automatically switch lenses
|
|
1264
|
+
try {
|
|
1265
|
+
ListenableFuture<Void> zoomFuture = camera
|
|
1266
|
+
.getCameraControl()
|
|
1267
|
+
.setZoomRatio(zoomRatio);
|
|
1268
|
+
|
|
1269
|
+
// Add callback to see what actually happened
|
|
1270
|
+
zoomFuture.addListener(
|
|
1271
|
+
() -> {
|
|
1272
|
+
try {
|
|
1273
|
+
float actualZoom = Objects.requireNonNull(
|
|
1274
|
+
camera.getCameraInfo().getZoomState().getValue()
|
|
1275
|
+
).getZoomRatio();
|
|
1276
|
+
Log.d(
|
|
1277
|
+
TAG,
|
|
1278
|
+
"setZoom: CameraX set zoom to " +
|
|
1279
|
+
actualZoom +
|
|
1280
|
+
" (requested: " +
|
|
1281
|
+
zoomRatio +
|
|
1282
|
+
")"
|
|
1283
|
+
);
|
|
1284
|
+
if (Math.abs(actualZoom - zoomRatio) > 0.1f) {
|
|
1285
|
+
Log.w(
|
|
1286
|
+
TAG,
|
|
1287
|
+
"setZoom: CameraX clamped zoom from " +
|
|
1288
|
+
zoomRatio +
|
|
1289
|
+
" to " +
|
|
1290
|
+
actualZoom
|
|
1291
|
+
);
|
|
965
1292
|
} else {
|
|
966
|
-
|
|
1293
|
+
Log.d(TAG, "setZoom: CameraX successfully set requested zoom");
|
|
967
1294
|
}
|
|
1295
|
+
} catch (Exception e) {
|
|
1296
|
+
Log.e(TAG, "setZoom: Error checking final zoom", e);
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
mainExecutor
|
|
1300
|
+
);
|
|
1301
|
+
} catch (Exception e) {
|
|
1302
|
+
Log.e(TAG, "setZoom: Failed to set zoom to " + zoomRatio, e);
|
|
1303
|
+
throw e;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
public static List<Size> getSupportedPictureSizes(String facing) {
|
|
1308
|
+
List<Size> sizes = new ArrayList<>();
|
|
1309
|
+
try {
|
|
1310
|
+
CameraSelector.Builder builder = new CameraSelector.Builder();
|
|
1311
|
+
if ("front".equals(facing)) {
|
|
1312
|
+
builder.requireLensFacing(CameraSelector.LENS_FACING_FRONT);
|
|
1313
|
+
} else {
|
|
1314
|
+
builder.requireLensFacing(CameraSelector.LENS_FACING_BACK);
|
|
1315
|
+
}
|
|
968
1316
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
Log.e(TAG, "Error getting supported picture sizes", e);
|
|
979
|
-
}
|
|
980
|
-
return sizes;
|
|
1317
|
+
// This part is complex because we need characteristics, which are not directly on CameraInfo.
|
|
1318
|
+
// For now, returning a static list of common sizes.
|
|
1319
|
+
// A more advanced implementation would use Camera2interop to get StreamConfigurationMap.
|
|
1320
|
+
sizes.add(new Size(4032, 3024));
|
|
1321
|
+
sizes.add(new Size(1920, 1080));
|
|
1322
|
+
sizes.add(new Size(1280, 720));
|
|
1323
|
+
sizes.add(new Size(640, 480));
|
|
1324
|
+
} catch (Exception e) {
|
|
1325
|
+
Log.e(TAG, "Error getting supported picture sizes", e);
|
|
981
1326
|
}
|
|
1327
|
+
return sizes;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
private void setZoomInternal(float zoomRatio) {
|
|
1331
|
+
if (camera != null) {
|
|
1332
|
+
try {
|
|
1333
|
+
float minZoom = Objects.requireNonNull(
|
|
1334
|
+
camera.getCameraInfo().getZoomState().getValue()
|
|
1335
|
+
).getMinZoomRatio();
|
|
1336
|
+
float maxZoom = camera
|
|
1337
|
+
.getCameraInfo()
|
|
1338
|
+
.getZoomState()
|
|
1339
|
+
.getValue()
|
|
1340
|
+
.getMaxZoomRatio();
|
|
1341
|
+
float currentZoom = camera
|
|
1342
|
+
.getCameraInfo()
|
|
1343
|
+
.getZoomState()
|
|
1344
|
+
.getValue()
|
|
1345
|
+
.getZoomRatio();
|
|
1346
|
+
|
|
1347
|
+
Log.d(
|
|
1348
|
+
TAG,
|
|
1349
|
+
"setZoomInternal: Current camera range: " +
|
|
1350
|
+
minZoom +
|
|
1351
|
+
"-" +
|
|
1352
|
+
maxZoom +
|
|
1353
|
+
", current: " +
|
|
1354
|
+
currentZoom
|
|
1355
|
+
);
|
|
1356
|
+
Log.d(TAG, "setZoomInternal: Requesting zoom: " + zoomRatio);
|
|
982
1357
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
float maxZoom = camera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
|
|
988
|
-
float currentZoom = camera.getCameraInfo().getZoomState().getValue().getZoomRatio();
|
|
989
|
-
|
|
990
|
-
Log.d(TAG, "setZoomInternal: Current camera range: " + minZoom + "-" + maxZoom + ", current: " + currentZoom);
|
|
991
|
-
Log.d(TAG, "setZoomInternal: Requesting zoom: " + zoomRatio);
|
|
992
|
-
|
|
993
|
-
// Try to set zoom directly - let CameraX handle lens switching
|
|
994
|
-
ListenableFuture<Void> zoomFuture = camera.getCameraControl().setZoomRatio(zoomRatio);
|
|
995
|
-
|
|
996
|
-
zoomFuture.addListener(() -> {
|
|
997
|
-
try {
|
|
998
|
-
zoomFuture.get(); // Check if zoom was successful
|
|
999
|
-
float newZoom = Objects.requireNonNull(camera.getCameraInfo().getZoomState().getValue()).getZoomRatio();
|
|
1000
|
-
Log.d(TAG, "setZoomInternal: Zoom set successfully to " + newZoom + " (requested: " + zoomRatio + ")");
|
|
1001
|
-
|
|
1002
|
-
// Check if CameraX switched cameras
|
|
1003
|
-
String newCameraId = getCameraId(camera.getCameraInfo());
|
|
1004
|
-
if (!newCameraId.equals(currentDeviceId)) {
|
|
1005
|
-
currentDeviceId = newCameraId;
|
|
1006
|
-
Log.d(TAG, "setZoomInternal: CameraX switched to camera: " + newCameraId);
|
|
1007
|
-
}
|
|
1008
|
-
} catch (Exception e) {
|
|
1009
|
-
Log.w(TAG, "setZoomInternal: Zoom operation failed: " + e.getMessage());
|
|
1010
|
-
// Fallback: clamp to current camera's range
|
|
1011
|
-
float clampedZoom = Math.max(minZoom, Math.min(zoomRatio, maxZoom));
|
|
1012
|
-
camera.getCameraControl().setZoomRatio(clampedZoom);
|
|
1013
|
-
Log.d(TAG, "setZoomInternal: Fallback - clamped zoom to " + clampedZoom);
|
|
1014
|
-
}
|
|
1015
|
-
}, mainExecutor);
|
|
1358
|
+
// Try to set zoom directly - let CameraX handle lens switching
|
|
1359
|
+
ListenableFuture<Void> zoomFuture = camera
|
|
1360
|
+
.getCameraControl()
|
|
1361
|
+
.setZoomRatio(zoomRatio);
|
|
1016
1362
|
|
|
1363
|
+
zoomFuture.addListener(
|
|
1364
|
+
() -> {
|
|
1365
|
+
try {
|
|
1366
|
+
zoomFuture.get(); // Check if zoom was successful
|
|
1367
|
+
float newZoom = Objects.requireNonNull(
|
|
1368
|
+
camera.getCameraInfo().getZoomState().getValue()
|
|
1369
|
+
).getZoomRatio();
|
|
1370
|
+
Log.d(
|
|
1371
|
+
TAG,
|
|
1372
|
+
"setZoomInternal: Zoom set successfully to " +
|
|
1373
|
+
newZoom +
|
|
1374
|
+
" (requested: " +
|
|
1375
|
+
zoomRatio +
|
|
1376
|
+
")"
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
// Check if CameraX switched cameras
|
|
1380
|
+
String newCameraId = getCameraId(camera.getCameraInfo());
|
|
1381
|
+
if (!newCameraId.equals(currentDeviceId)) {
|
|
1382
|
+
currentDeviceId = newCameraId;
|
|
1383
|
+
Log.d(
|
|
1384
|
+
TAG,
|
|
1385
|
+
"setZoomInternal: CameraX switched to camera: " + newCameraId
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1017
1388
|
} catch (Exception e) {
|
|
1018
|
-
|
|
1389
|
+
Log.w(
|
|
1390
|
+
TAG,
|
|
1391
|
+
"setZoomInternal: Zoom operation failed: " + e.getMessage()
|
|
1392
|
+
);
|
|
1393
|
+
// Fallback: clamp to current camera's range
|
|
1394
|
+
float clampedZoom = Math.max(
|
|
1395
|
+
minZoom,
|
|
1396
|
+
Math.min(zoomRatio, maxZoom)
|
|
1397
|
+
);
|
|
1398
|
+
camera.getCameraControl().setZoomRatio(clampedZoom);
|
|
1399
|
+
Log.d(
|
|
1400
|
+
TAG,
|
|
1401
|
+
"setZoomInternal: Fallback - clamped zoom to " + clampedZoom
|
|
1402
|
+
);
|
|
1019
1403
|
}
|
|
1020
|
-
|
|
1404
|
+
},
|
|
1405
|
+
mainExecutor
|
|
1406
|
+
);
|
|
1407
|
+
} catch (Exception e) {
|
|
1408
|
+
Log.e(TAG, "setZoomInternal: Error setting zoom", e);
|
|
1409
|
+
}
|
|
1021
1410
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
public static List<String> getSupportedFlashModesStatic() {
|
|
1414
|
+
try {
|
|
1415
|
+
// For static method, we can return common flash modes
|
|
1416
|
+
// Most modern cameras support these modes
|
|
1417
|
+
return Arrays.asList("off", "on", "auto");
|
|
1418
|
+
} catch (Exception e) {
|
|
1419
|
+
Log.e(TAG, "getSupportedFlashModesStatic: Error getting flash modes", e);
|
|
1420
|
+
return Collections.singletonList("off");
|
|
1032
1421
|
}
|
|
1422
|
+
}
|
|
1033
1423
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
try {
|
|
1040
|
-
boolean hasFlash = camera.getCameraInfo().hasFlashUnit();
|
|
1041
|
-
if (hasFlash) {
|
|
1042
|
-
return Arrays.asList("off", "on", "auto");
|
|
1043
|
-
} else {
|
|
1044
|
-
return Collections.singletonList("off");
|
|
1045
|
-
}
|
|
1046
|
-
} catch (Exception e) {
|
|
1047
|
-
Log.e(TAG, "getSupportedFlashModes: Error getting flash modes", e);
|
|
1048
|
-
return Collections.singletonList("off");
|
|
1049
|
-
}
|
|
1424
|
+
public List<String> getSupportedFlashModes() {
|
|
1425
|
+
if (camera == null) {
|
|
1426
|
+
return getSupportedFlashModesStatic();
|
|
1050
1427
|
}
|
|
1051
1428
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1429
|
+
try {
|
|
1430
|
+
boolean hasFlash = camera.getCameraInfo().hasFlashUnit();
|
|
1431
|
+
if (hasFlash) {
|
|
1432
|
+
return Arrays.asList("off", "on", "auto");
|
|
1433
|
+
} else {
|
|
1434
|
+
return Collections.singletonList("off");
|
|
1435
|
+
}
|
|
1436
|
+
} catch (Exception e) {
|
|
1437
|
+
Log.e(TAG, "getSupportedFlashModes: Error getting flash modes", e);
|
|
1438
|
+
return Collections.singletonList("off");
|
|
1061
1439
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
default:
|
|
1073
|
-
flashMode = ImageCapture.FLASH_MODE_OFF;
|
|
1074
|
-
break;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
currentFlashMode = flashMode;
|
|
1078
|
-
|
|
1079
|
-
if (imageCapture != null) {
|
|
1080
|
-
imageCapture.setFlashMode(flashMode);
|
|
1081
|
-
}
|
|
1082
|
-
if (sampleImageCapture != null) {
|
|
1083
|
-
sampleImageCapture.setFlashMode(flashMode);
|
|
1084
|
-
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
public String getFlashMode() {
|
|
1443
|
+
switch (currentFlashMode) {
|
|
1444
|
+
case ImageCapture.FLASH_MODE_ON:
|
|
1445
|
+
return "on";
|
|
1446
|
+
case ImageCapture.FLASH_MODE_AUTO:
|
|
1447
|
+
return "auto";
|
|
1448
|
+
default:
|
|
1449
|
+
return "off";
|
|
1085
1450
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
public void setFlashMode(String mode) {
|
|
1454
|
+
int flashMode;
|
|
1455
|
+
switch (mode) {
|
|
1456
|
+
case "on":
|
|
1457
|
+
flashMode = ImageCapture.FLASH_MODE_ON;
|
|
1458
|
+
break;
|
|
1459
|
+
case "auto":
|
|
1460
|
+
flashMode = ImageCapture.FLASH_MODE_AUTO;
|
|
1461
|
+
break;
|
|
1462
|
+
default:
|
|
1463
|
+
flashMode = ImageCapture.FLASH_MODE_OFF;
|
|
1464
|
+
break;
|
|
1089
1465
|
}
|
|
1090
1466
|
|
|
1091
|
-
|
|
1092
|
-
public void switchToDevice(String deviceId) {
|
|
1093
|
-
Log.d(TAG, "switchToDevice: Attempting to switch to device " + deviceId);
|
|
1467
|
+
currentFlashMode = flashMode;
|
|
1094
1468
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
// Standard physical device selection logic...
|
|
1098
|
-
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
|
|
1099
|
-
CameraInfo targetCameraInfo = null;
|
|
1100
|
-
for (CameraInfo cameraInfo : cameraInfos) {
|
|
1101
|
-
if (deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
|
|
1102
|
-
targetCameraInfo = cameraInfo;
|
|
1103
|
-
break;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
if (targetCameraInfo != null) {
|
|
1108
|
-
Log.d(TAG, "switchToDevice: Found matching CameraInfo for deviceId: " + deviceId);
|
|
1109
|
-
final CameraInfo finalTarget = targetCameraInfo;
|
|
1110
|
-
|
|
1111
|
-
// This filter will receive a list of all cameras and must return the one we want.
|
|
1112
|
-
|
|
1113
|
-
currentCameraSelector = new CameraSelector.Builder()
|
|
1114
|
-
.addCameraFilter(cameras -> {
|
|
1115
|
-
// This filter will receive a list of all cameras and must return the one we want.
|
|
1116
|
-
return Collections.singletonList(finalTarget);
|
|
1117
|
-
}).build();
|
|
1118
|
-
currentDeviceId = deviceId;
|
|
1119
|
-
bindCameraUseCases(); // Rebind with the new, highly specific selector
|
|
1120
|
-
} else {
|
|
1121
|
-
Log.e(TAG, "switchToDevice: Could not find any CameraInfo matching deviceId: " + deviceId);
|
|
1122
|
-
}
|
|
1123
|
-
} catch (Exception e) {
|
|
1124
|
-
Log.e(TAG, "switchToDevice: Error switching camera", e);
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1469
|
+
if (imageCapture != null) {
|
|
1470
|
+
imageCapture.setFlashMode(flashMode);
|
|
1127
1471
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
Log.d(TAG, "flipCamera: Flipping camera");
|
|
1131
|
-
|
|
1132
|
-
// Determine current position based on session config and flip it
|
|
1133
|
-
String currentPosition = sessionConfig.getPosition();
|
|
1134
|
-
String newPosition = "front".equals(currentPosition) ? "rear" : "front";
|
|
1135
|
-
|
|
1136
|
-
Log.d(TAG, "flipCamera: Switching from " + currentPosition + " to " + newPosition);
|
|
1137
|
-
|
|
1138
|
-
sessionConfig = new CameraSessionConfiguration(
|
|
1139
|
-
null, // deviceId - clear device ID to force position-based selection
|
|
1140
|
-
newPosition, // position
|
|
1141
|
-
sessionConfig.getX(), // x
|
|
1142
|
-
sessionConfig.getY(), // y
|
|
1143
|
-
sessionConfig.getWidth(), // width
|
|
1144
|
-
sessionConfig.getHeight(), // height
|
|
1145
|
-
sessionConfig.getPaddingBottom(), // paddingBottom
|
|
1146
|
-
sessionConfig.isToBack(), // toBack
|
|
1147
|
-
sessionConfig.isStoreToFile(), // storeToFile
|
|
1148
|
-
sessionConfig.isEnableOpacity(), // enableOpacity
|
|
1149
|
-
sessionConfig.isEnableZoom(), // enableZoom
|
|
1150
|
-
sessionConfig.isDisableExifHeaderStripping(), // disableExifHeaderStripping
|
|
1151
|
-
sessionConfig.isDisableAudio(), // disableAudio
|
|
1152
|
-
sessionConfig.getZoomFactor(), // zoomFactor
|
|
1153
|
-
sessionConfig.getAspectRatio(), // aspectRatio
|
|
1154
|
-
sessionConfig.getGridMode() // gridMode
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
// Clear current device ID to force position-based selection
|
|
1158
|
-
currentDeviceId = null;
|
|
1159
|
-
|
|
1160
|
-
// Camera operations must run on main thread
|
|
1161
|
-
cameraExecutor.execute(() -> {
|
|
1162
|
-
currentCameraSelector = buildCameraSelector();
|
|
1163
|
-
bindCameraUseCases();
|
|
1164
|
-
});
|
|
1472
|
+
if (sampleImageCapture != null) {
|
|
1473
|
+
sampleImageCapture.setFlashMode(flashMode);
|
|
1165
1474
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
public String getCurrentDeviceId() {
|
|
1478
|
+
return currentDeviceId != null ? currentDeviceId : "unknown";
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1482
|
+
public void switchToDevice(String deviceId) {
|
|
1483
|
+
Log.d(TAG, "switchToDevice: Attempting to switch to device " + deviceId);
|
|
1484
|
+
|
|
1485
|
+
mainExecutor.execute(() -> {
|
|
1486
|
+
try {
|
|
1487
|
+
// Standard physical device selection logic...
|
|
1488
|
+
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
|
|
1489
|
+
CameraInfo targetCameraInfo = null;
|
|
1490
|
+
for (CameraInfo cameraInfo : cameraInfos) {
|
|
1491
|
+
if (
|
|
1492
|
+
deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())
|
|
1493
|
+
) {
|
|
1494
|
+
targetCameraInfo = cameraInfo;
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1170
1497
|
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
private void updateLayoutParams() {
|
|
1174
|
-
if (sessionConfig == null) return;
|
|
1175
|
-
|
|
1176
|
-
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
|
1177
|
-
sessionConfig.getWidth(),
|
|
1178
|
-
sessionConfig.getHeight()
|
|
1179
|
-
);
|
|
1180
|
-
layoutParams.leftMargin = sessionConfig.getX();
|
|
1181
|
-
layoutParams.topMargin = sessionConfig.getY();
|
|
1182
1498
|
|
|
1183
|
-
if (
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1499
|
+
if (targetCameraInfo != null) {
|
|
1500
|
+
Log.d(
|
|
1501
|
+
TAG,
|
|
1502
|
+
"switchToDevice: Found matching CameraInfo for deviceId: " +
|
|
1503
|
+
deviceId
|
|
1504
|
+
);
|
|
1505
|
+
final CameraInfo finalTarget = targetCameraInfo;
|
|
1506
|
+
|
|
1507
|
+
// This filter will receive a list of all cameras and must return the one we want.
|
|
1508
|
+
|
|
1509
|
+
currentCameraSelector = new CameraSelector.Builder()
|
|
1510
|
+
.addCameraFilter(cameras -> {
|
|
1511
|
+
// This filter will receive a list of all cameras and must return the one we want.
|
|
1512
|
+
return Collections.singletonList(finalTarget);
|
|
1513
|
+
})
|
|
1514
|
+
.build();
|
|
1515
|
+
currentDeviceId = deviceId;
|
|
1516
|
+
bindCameraUseCases(); // Rebind with the new, highly specific selector
|
|
1517
|
+
} else {
|
|
1518
|
+
Log.e(
|
|
1519
|
+
TAG,
|
|
1520
|
+
"switchToDevice: Could not find any CameraInfo matching deviceId: " +
|
|
1521
|
+
deviceId
|
|
1522
|
+
);
|
|
1192
1523
|
}
|
|
1524
|
+
} catch (Exception e) {
|
|
1525
|
+
Log.e(TAG, "switchToDevice: Error switching camera", e);
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
public void flipCamera() {
|
|
1531
|
+
Log.d(TAG, "flipCamera: Flipping camera");
|
|
1532
|
+
|
|
1533
|
+
// Determine current position based on session config and flip it
|
|
1534
|
+
String currentPosition = sessionConfig.getPosition();
|
|
1535
|
+
String newPosition = "front".equals(currentPosition) ? "rear" : "front";
|
|
1536
|
+
|
|
1537
|
+
Log.d(
|
|
1538
|
+
TAG,
|
|
1539
|
+
"flipCamera: Switching from " + currentPosition + " to " + newPosition
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
1543
|
+
null, // deviceId - clear device ID to force position-based selection
|
|
1544
|
+
newPosition, // position
|
|
1545
|
+
sessionConfig.getX(), // x
|
|
1546
|
+
sessionConfig.getY(), // y
|
|
1547
|
+
sessionConfig.getWidth(), // width
|
|
1548
|
+
sessionConfig.getHeight(), // height
|
|
1549
|
+
sessionConfig.getPaddingBottom(), // paddingBottom
|
|
1550
|
+
sessionConfig.isToBack(), // toBack
|
|
1551
|
+
sessionConfig.isStoreToFile(), // storeToFile
|
|
1552
|
+
sessionConfig.isEnableOpacity(), // enableOpacity
|
|
1553
|
+
sessionConfig.isEnableZoom(), // enableZoom
|
|
1554
|
+
sessionConfig.isDisableExifHeaderStripping(), // disableExifHeaderStripping
|
|
1555
|
+
sessionConfig.isDisableAudio(), // disableAudio
|
|
1556
|
+
sessionConfig.getZoomFactor(), // zoomFactor
|
|
1557
|
+
sessionConfig.getAspectRatio(), // aspectRatio
|
|
1558
|
+
sessionConfig.getGridMode() // gridMode
|
|
1559
|
+
);
|
|
1560
|
+
|
|
1561
|
+
// Clear current device ID to force position-based selection
|
|
1562
|
+
currentDeviceId = null;
|
|
1563
|
+
|
|
1564
|
+
// Camera operations must run on main thread
|
|
1565
|
+
cameraExecutor.execute(() -> {
|
|
1566
|
+
currentCameraSelector = buildCameraSelector();
|
|
1567
|
+
bindCameraUseCases();
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
public void setOpacity(float opacity) {
|
|
1572
|
+
if (previewView != null) {
|
|
1573
|
+
previewView.setAlpha(opacity);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
private void updateLayoutParams() {
|
|
1578
|
+
if (sessionConfig == null) return;
|
|
1579
|
+
|
|
1580
|
+
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
|
1581
|
+
sessionConfig.getWidth(),
|
|
1582
|
+
sessionConfig.getHeight()
|
|
1583
|
+
);
|
|
1584
|
+
layoutParams.leftMargin = sessionConfig.getX();
|
|
1585
|
+
layoutParams.topMargin = sessionConfig.getY();
|
|
1586
|
+
|
|
1587
|
+
if (sessionConfig.getAspectRatio() != null) {
|
|
1588
|
+
String[] ratios = sessionConfig.getAspectRatio().split(":");
|
|
1589
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
1590
|
+
float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
|
|
1591
|
+
if (sessionConfig.getWidth() > 0) {
|
|
1592
|
+
layoutParams.height = (int) (sessionConfig.getWidth() / ratio);
|
|
1593
|
+
} else if (sessionConfig.getHeight() > 0) {
|
|
1594
|
+
layoutParams.width = (int) (sessionConfig.getHeight() * ratio);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1193
1597
|
|
|
1194
|
-
|
|
1598
|
+
previewView.setLayoutParams(layoutParams);
|
|
1195
1599
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1600
|
+
if (listener != null) {
|
|
1601
|
+
listener.onCameraStarted(
|
|
1602
|
+
sessionConfig.getWidth(),
|
|
1603
|
+
sessionConfig.getHeight(),
|
|
1604
|
+
sessionConfig.getX(),
|
|
1605
|
+
sessionConfig.getY()
|
|
1606
|
+
);
|
|
1204
1607
|
}
|
|
1608
|
+
}
|
|
1205
1609
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
}
|
|
1210
|
-
return "4:3";
|
|
1610
|
+
public String getAspectRatio() {
|
|
1611
|
+
if (sessionConfig != null) {
|
|
1612
|
+
return sessionConfig.getAspectRatio();
|
|
1211
1613
|
}
|
|
1614
|
+
return "4:3";
|
|
1615
|
+
}
|
|
1212
1616
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1217
|
-
return "none";
|
|
1617
|
+
public String getGridMode() {
|
|
1618
|
+
if (sessionConfig != null) {
|
|
1619
|
+
return sessionConfig.getGridMode();
|
|
1218
1620
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1621
|
+
return "none";
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
public void setAspectRatio(String aspectRatio) {
|
|
1625
|
+
setAspectRatio(aspectRatio, null, null);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
public void setAspectRatio(String aspectRatio, Float x, Float y) {
|
|
1629
|
+
setAspectRatio(aspectRatio, x, y, null);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
public void setAspectRatio(
|
|
1633
|
+
String aspectRatio,
|
|
1634
|
+
Float x,
|
|
1635
|
+
Float y,
|
|
1636
|
+
Runnable callback
|
|
1637
|
+
) {
|
|
1638
|
+
if (sessionConfig == null) {
|
|
1639
|
+
if (callback != null) callback.run();
|
|
1640
|
+
return;
|
|
1222
1641
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1642
|
+
|
|
1643
|
+
String currentAspectRatio = sessionConfig.getAspectRatio();
|
|
1644
|
+
|
|
1645
|
+
// Don't restart camera if aspect ratio hasn't changed and no position specified
|
|
1646
|
+
if (
|
|
1647
|
+
aspectRatio != null &&
|
|
1648
|
+
aspectRatio.equals(currentAspectRatio) &&
|
|
1649
|
+
x == null &&
|
|
1650
|
+
y == null
|
|
1651
|
+
) {
|
|
1652
|
+
Log.d(
|
|
1653
|
+
TAG,
|
|
1654
|
+
"setAspectRatio: Aspect ratio " +
|
|
1655
|
+
aspectRatio +
|
|
1656
|
+
" is already set and no position specified, skipping"
|
|
1657
|
+
);
|
|
1658
|
+
if (callback != null) callback.run();
|
|
1659
|
+
return;
|
|
1226
1660
|
}
|
|
1227
1661
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1662
|
+
String currentGridMode = sessionConfig.getGridMode();
|
|
1663
|
+
Log.d(
|
|
1664
|
+
TAG,
|
|
1665
|
+
"setAspectRatio: Changing from " +
|
|
1666
|
+
currentAspectRatio +
|
|
1667
|
+
" to " +
|
|
1668
|
+
aspectRatio +
|
|
1669
|
+
(x != null && y != null
|
|
1670
|
+
? " at position (" + x + ", " + y + ")"
|
|
1671
|
+
: " with auto-centering") +
|
|
1672
|
+
", preserving grid mode: " +
|
|
1673
|
+
currentGridMode
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
1677
|
+
sessionConfig.getDeviceId(),
|
|
1678
|
+
sessionConfig.getPosition(),
|
|
1679
|
+
sessionConfig.getX(),
|
|
1680
|
+
sessionConfig.getY(),
|
|
1681
|
+
sessionConfig.getWidth(),
|
|
1682
|
+
sessionConfig.getHeight(),
|
|
1683
|
+
sessionConfig.getPaddingBottom(),
|
|
1684
|
+
sessionConfig.getToBack(),
|
|
1685
|
+
sessionConfig.getStoreToFile(),
|
|
1686
|
+
sessionConfig.getEnableOpacity(),
|
|
1687
|
+
sessionConfig.getEnableZoom(),
|
|
1688
|
+
sessionConfig.getDisableExifHeaderStripping(),
|
|
1689
|
+
sessionConfig.getDisableAudio(),
|
|
1690
|
+
sessionConfig.getZoomFactor(),
|
|
1691
|
+
aspectRatio,
|
|
1692
|
+
currentGridMode
|
|
1693
|
+
);
|
|
1694
|
+
|
|
1695
|
+
// Update layout and rebind camera with new aspect ratio
|
|
1696
|
+
if (isRunning && previewContainer != null) {
|
|
1697
|
+
mainExecutor.execute(() -> {
|
|
1698
|
+
// First update the UI layout
|
|
1699
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, x, y);
|
|
1700
|
+
|
|
1701
|
+
// Then rebind the camera with new aspect ratio configuration
|
|
1702
|
+
Log.d(
|
|
1703
|
+
TAG,
|
|
1704
|
+
"setAspectRatio: Rebinding camera with new aspect ratio: " +
|
|
1705
|
+
aspectRatio
|
|
1265
1706
|
);
|
|
1266
|
-
|
|
1267
|
-
// Update layout and rebind camera with new aspect ratio
|
|
1268
|
-
if (isRunning && previewContainer != null) {
|
|
1269
|
-
mainExecutor.execute(() -> {
|
|
1270
|
-
// First update the UI layout
|
|
1271
|
-
updatePreviewLayoutForAspectRatio(aspectRatio, x, y);
|
|
1272
|
-
|
|
1273
|
-
// Then rebind the camera with new aspect ratio configuration
|
|
1274
|
-
Log.d(TAG, "setAspectRatio: Rebinding camera with new aspect ratio: " + aspectRatio);
|
|
1275
|
-
bindCameraUseCases();
|
|
1276
|
-
|
|
1277
|
-
// Preserve grid mode and wait for completion
|
|
1278
|
-
if (gridOverlayView != null) {
|
|
1279
|
-
gridOverlayView.post(() -> {
|
|
1280
|
-
Log.d(TAG, "setAspectRatio: Re-applying grid mode: " + currentGridMode);
|
|
1281
|
-
gridOverlayView.setGridMode(currentGridMode);
|
|
1282
|
-
|
|
1283
|
-
// Wait one more frame for grid to be applied, then call callback
|
|
1284
|
-
if (callback != null) {
|
|
1285
|
-
gridOverlayView.post(callback);
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
} else {
|
|
1289
|
-
// No grid overlay, wait one frame for layout completion then call callback
|
|
1290
|
-
if (callback != null) {
|
|
1291
|
-
previewContainer.post(callback);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
});
|
|
1295
|
-
} else {
|
|
1296
|
-
if (callback != null) callback.run();
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1707
|
+
bindCameraUseCases();
|
|
1299
1708
|
|
|
1300
|
-
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
sessionConfig.getX(),
|
|
1307
|
-
sessionConfig.getY(),
|
|
1308
|
-
sessionConfig.getWidth(),
|
|
1309
|
-
sessionConfig.getHeight(),
|
|
1310
|
-
sessionConfig.getPaddingBottom(),
|
|
1311
|
-
sessionConfig.getToBack(),
|
|
1312
|
-
sessionConfig.getStoreToFile(),
|
|
1313
|
-
sessionConfig.getEnableOpacity(),
|
|
1314
|
-
sessionConfig.getEnableZoom(),
|
|
1315
|
-
sessionConfig.getDisableExifHeaderStripping(),
|
|
1316
|
-
sessionConfig.getDisableAudio(),
|
|
1317
|
-
sessionConfig.getZoomFactor(),
|
|
1318
|
-
sessionConfig.getAspectRatio(),
|
|
1319
|
-
gridMode
|
|
1709
|
+
// Preserve grid mode and wait for completion
|
|
1710
|
+
if (gridOverlayView != null) {
|
|
1711
|
+
gridOverlayView.post(() -> {
|
|
1712
|
+
Log.d(
|
|
1713
|
+
TAG,
|
|
1714
|
+
"setAspectRatio: Re-applying grid mode: " + currentGridMode
|
|
1320
1715
|
);
|
|
1716
|
+
gridOverlayView.setGridMode(currentGridMode);
|
|
1321
1717
|
|
|
1322
|
-
//
|
|
1323
|
-
if (
|
|
1324
|
-
|
|
1325
|
-
Log.d(TAG, "setGridMode: Applying grid mode to overlay: " + gridMode);
|
|
1326
|
-
gridOverlayView.setGridMode(gridMode);
|
|
1327
|
-
});
|
|
1718
|
+
// Wait one more frame for grid to be applied, then call callback
|
|
1719
|
+
if (callback != null) {
|
|
1720
|
+
gridOverlayView.post(callback);
|
|
1328
1721
|
}
|
|
1722
|
+
});
|
|
1723
|
+
} else {
|
|
1724
|
+
// No grid overlay, wait one frame for layout completion then call callback
|
|
1725
|
+
if (callback != null) {
|
|
1726
|
+
previewContainer.post(callback);
|
|
1727
|
+
}
|
|
1329
1728
|
}
|
|
1729
|
+
});
|
|
1730
|
+
} else {
|
|
1731
|
+
if (callback != null) callback.run();
|
|
1330
1732
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
public void setGridMode(String gridMode) {
|
|
1736
|
+
if (sessionConfig != null) {
|
|
1737
|
+
Log.d(TAG, "setGridMode: Changing grid mode to: " + gridMode);
|
|
1738
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
1739
|
+
sessionConfig.getDeviceId(),
|
|
1740
|
+
sessionConfig.getPosition(),
|
|
1741
|
+
sessionConfig.getX(),
|
|
1742
|
+
sessionConfig.getY(),
|
|
1743
|
+
sessionConfig.getWidth(),
|
|
1744
|
+
sessionConfig.getHeight(),
|
|
1745
|
+
sessionConfig.getPaddingBottom(),
|
|
1746
|
+
sessionConfig.getToBack(),
|
|
1747
|
+
sessionConfig.getStoreToFile(),
|
|
1748
|
+
sessionConfig.getEnableOpacity(),
|
|
1749
|
+
sessionConfig.getEnableZoom(),
|
|
1750
|
+
sessionConfig.getDisableExifHeaderStripping(),
|
|
1751
|
+
sessionConfig.getDisableAudio(),
|
|
1752
|
+
sessionConfig.getZoomFactor(),
|
|
1753
|
+
sessionConfig.getAspectRatio(),
|
|
1754
|
+
gridMode
|
|
1755
|
+
);
|
|
1756
|
+
|
|
1757
|
+
// Update the grid overlay immediately
|
|
1758
|
+
if (gridOverlayView != null) {
|
|
1759
|
+
gridOverlayView.post(() -> {
|
|
1760
|
+
Log.d(TAG, "setGridMode: Applying grid mode to overlay: " + gridMode);
|
|
1761
|
+
gridOverlayView.setGridMode(gridMode);
|
|
1762
|
+
});
|
|
1355
1763
|
}
|
|
1356
|
-
return previewContainer.getTop();
|
|
1357
1764
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
public int getPreviewX() {
|
|
1768
|
+
if (previewContainer == null) return 0;
|
|
1769
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
1770
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
1771
|
+
// Return position relative to WebView content (subtract insets)
|
|
1772
|
+
int margin = ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
|
|
1773
|
+
int leftInset = getWebViewLeftInset();
|
|
1774
|
+
int result = margin - leftInset;
|
|
1775
|
+
Log.d(
|
|
1776
|
+
TAG,
|
|
1777
|
+
"getPreviewX: leftMargin=" +
|
|
1778
|
+
margin +
|
|
1779
|
+
", leftInset=" +
|
|
1780
|
+
leftInset +
|
|
1781
|
+
", result=" +
|
|
1782
|
+
result
|
|
1783
|
+
);
|
|
1784
|
+
return result;
|
|
1360
1785
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1786
|
+
return previewContainer.getLeft();
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
public int getPreviewY() {
|
|
1790
|
+
if (previewContainer == null) return 0;
|
|
1791
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
1792
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
1793
|
+
// Return position relative to WebView content (subtract insets)
|
|
1794
|
+
int margin = ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
|
|
1795
|
+
int topInset = getWebViewTopInset();
|
|
1796
|
+
int result = margin - topInset;
|
|
1797
|
+
Log.d(
|
|
1798
|
+
TAG,
|
|
1799
|
+
"getPreviewY: topMargin=" +
|
|
1800
|
+
margin +
|
|
1801
|
+
", topInset=" +
|
|
1802
|
+
topInset +
|
|
1803
|
+
", result=" +
|
|
1804
|
+
result
|
|
1805
|
+
);
|
|
1806
|
+
return result;
|
|
1363
1807
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1808
|
+
return previewContainer.getTop();
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
public int getPreviewWidth() {
|
|
1812
|
+
return previewContainer != null ? previewContainer.getWidth() : 0;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
public int getPreviewHeight() {
|
|
1816
|
+
return previewContainer != null ? previewContainer.getHeight() : 0;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
public void setPreviewSize(int x, int y, int width, int height) {
|
|
1820
|
+
setPreviewSize(x, y, width, height, null);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
public void setPreviewSize(
|
|
1824
|
+
int x,
|
|
1825
|
+
int y,
|
|
1826
|
+
int width,
|
|
1827
|
+
int height,
|
|
1828
|
+
Runnable callback
|
|
1829
|
+
) {
|
|
1830
|
+
if (previewContainer == null) {
|
|
1831
|
+
if (callback != null) callback.run();
|
|
1832
|
+
return;
|
|
1366
1833
|
}
|
|
1367
1834
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1835
|
+
// Ensure this runs on the main UI thread
|
|
1836
|
+
mainExecutor.execute(() -> {
|
|
1837
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
1838
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
1839
|
+
ViewGroup.MarginLayoutParams params =
|
|
1840
|
+
(ViewGroup.MarginLayoutParams) layoutParams;
|
|
1841
|
+
|
|
1842
|
+
// Only add insets for positioning coordinates, not for full-screen sizes
|
|
1843
|
+
int webViewTopInset = getWebViewTopInset();
|
|
1844
|
+
int webViewLeftInset = getWebViewLeftInset();
|
|
1845
|
+
|
|
1846
|
+
// Handle positioning - preserve current values if new values are not specified (negative)
|
|
1847
|
+
if (x >= 0) {
|
|
1848
|
+
// Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
|
|
1849
|
+
if (x == 0 && y == 0) {
|
|
1850
|
+
params.leftMargin = x;
|
|
1851
|
+
Log.d(
|
|
1852
|
+
TAG,
|
|
1853
|
+
"setPreviewSize: Full-screen mode - keeping x=0 without insets"
|
|
1854
|
+
);
|
|
1855
|
+
} else {
|
|
1856
|
+
params.leftMargin = x + webViewLeftInset;
|
|
1857
|
+
Log.d(
|
|
1858
|
+
TAG,
|
|
1859
|
+
"setPreviewSize: Positioned mode - x=" +
|
|
1860
|
+
x +
|
|
1861
|
+
" + inset=" +
|
|
1862
|
+
webViewLeftInset +
|
|
1863
|
+
" = " +
|
|
1864
|
+
(x + webViewLeftInset)
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1372
1867
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
1432
|
-
Log.d(TAG, "setPreviewSize: Calculated aspect ratio from " + params.width + "x" + params.height + " = " + calculatedAspectRatio + " (normalized ratio=" + ratio + ")");
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
sessionConfig = new CameraSessionConfiguration(
|
|
1436
|
-
sessionConfig.getDeviceId(),
|
|
1437
|
-
sessionConfig.getPosition(),
|
|
1438
|
-
params.leftMargin,
|
|
1439
|
-
params.topMargin,
|
|
1440
|
-
params.width,
|
|
1441
|
-
params.height,
|
|
1442
|
-
sessionConfig.getPaddingBottom(),
|
|
1443
|
-
sessionConfig.getToBack(),
|
|
1444
|
-
sessionConfig.getStoreToFile(),
|
|
1445
|
-
sessionConfig.getEnableOpacity(),
|
|
1446
|
-
sessionConfig.getEnableZoom(),
|
|
1447
|
-
sessionConfig.getDisableExifHeaderStripping(),
|
|
1448
|
-
sessionConfig.getDisableAudio(),
|
|
1449
|
-
sessionConfig.getZoomFactor(),
|
|
1450
|
-
calculatedAspectRatio,
|
|
1451
|
-
sessionConfig.getGridMode()
|
|
1452
|
-
);
|
|
1453
|
-
|
|
1454
|
-
// If aspect ratio changed due to size update, rebind camera
|
|
1455
|
-
if (isRunning && !Objects.equals(currentAspectRatio, calculatedAspectRatio)) {
|
|
1456
|
-
Log.d(TAG, "setPreviewSize: Aspect ratio changed from " + currentAspectRatio + " to " + calculatedAspectRatio + ", rebinding camera");
|
|
1457
|
-
bindCameraUseCases();
|
|
1458
|
-
|
|
1459
|
-
// Wait for camera rebinding to complete, then call callback
|
|
1460
|
-
if (callback != null) {
|
|
1461
|
-
previewContainer.post(() -> previewContainer.post(callback));
|
|
1462
|
-
}
|
|
1463
|
-
} else {
|
|
1464
|
-
// No camera rebinding needed, wait for layout to complete then call callback
|
|
1465
|
-
if (callback != null) {
|
|
1466
|
-
previewContainer.post(callback);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
} else {
|
|
1470
|
-
// No sessionConfig, just wait for layout then call callback
|
|
1471
|
-
if (callback != null) {
|
|
1472
|
-
previewContainer.post(callback);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1868
|
+
if (y >= 0) {
|
|
1869
|
+
// Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
|
|
1870
|
+
if (x == 0 && y == 0) {
|
|
1871
|
+
params.topMargin = y;
|
|
1872
|
+
Log.d(
|
|
1873
|
+
TAG,
|
|
1874
|
+
"setPreviewSize: Full-screen mode - keeping y=0 without insets"
|
|
1875
|
+
);
|
|
1876
|
+
} else {
|
|
1877
|
+
params.topMargin = y + webViewTopInset;
|
|
1878
|
+
Log.d(
|
|
1879
|
+
TAG,
|
|
1880
|
+
"setPreviewSize: Positioned mode - y=" +
|
|
1881
|
+
y +
|
|
1882
|
+
" + inset=" +
|
|
1883
|
+
webViewTopInset +
|
|
1884
|
+
" = " +
|
|
1885
|
+
(y + webViewTopInset)
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (width > 0) params.width = width;
|
|
1890
|
+
if (height > 0) params.height = height;
|
|
1891
|
+
|
|
1892
|
+
previewContainer.setLayoutParams(params);
|
|
1893
|
+
previewContainer.requestLayout();
|
|
1894
|
+
|
|
1895
|
+
Log.d(
|
|
1896
|
+
TAG,
|
|
1897
|
+
"setPreviewSize: Updated to " +
|
|
1898
|
+
params.width +
|
|
1899
|
+
"x" +
|
|
1900
|
+
params.height +
|
|
1901
|
+
" at (" +
|
|
1902
|
+
params.leftMargin +
|
|
1903
|
+
"," +
|
|
1904
|
+
params.topMargin +
|
|
1905
|
+
")"
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
// Update session config to reflect actual layout
|
|
1909
|
+
if (sessionConfig != null) {
|
|
1910
|
+
String currentAspectRatio = sessionConfig.getAspectRatio();
|
|
1911
|
+
|
|
1912
|
+
// Calculate aspect ratio from actual dimensions if both width and height are provided
|
|
1913
|
+
String calculatedAspectRatio = currentAspectRatio;
|
|
1914
|
+
if (params.width > 0 && params.height > 0) {
|
|
1915
|
+
// Always use larger dimension / smaller dimension for consistent comparison
|
|
1916
|
+
float ratio =
|
|
1917
|
+
Math.max(params.width, params.height) /
|
|
1918
|
+
(float) Math.min(params.width, params.height);
|
|
1919
|
+
// Standard ratios: 16:9 ≈ 1.778, 4:3 ≈ 1.333
|
|
1920
|
+
float ratio16_9 = 16f / 9f; // 1.778
|
|
1921
|
+
float ratio4_3 = 4f / 3f; // 1.333
|
|
1922
|
+
|
|
1923
|
+
// Determine closest standard aspect ratio
|
|
1924
|
+
if (Math.abs(ratio - ratio16_9) < Math.abs(ratio - ratio4_3)) {
|
|
1925
|
+
calculatedAspectRatio = "16:9";
|
|
1475
1926
|
} else {
|
|
1476
|
-
|
|
1477
|
-
// Fallback: just set width and height if specified
|
|
1478
|
-
if (width > 0) layoutParams.width = width;
|
|
1479
|
-
if (height > 0) layoutParams.height = height;
|
|
1480
|
-
previewContainer.setLayoutParams(layoutParams);
|
|
1481
|
-
previewContainer.requestLayout();
|
|
1482
|
-
|
|
1483
|
-
// Wait for layout then call callback
|
|
1484
|
-
if (callback != null) {
|
|
1485
|
-
previewContainer.post(callback);
|
|
1486
|
-
}
|
|
1927
|
+
calculatedAspectRatio = "4:3";
|
|
1487
1928
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
} else {
|
|
1540
|
-
// Auto-center the view
|
|
1541
|
-
// Calculate size based on aspect ratio, using a reasonable base size
|
|
1542
|
-
// Use 80% of available space to ensure aspect ratio differences are visible
|
|
1543
|
-
int maxAvailableWidth = (int) (availableWidth * 0.8);
|
|
1544
|
-
int maxAvailableHeight = (int) (availableHeight * 0.8);
|
|
1545
|
-
|
|
1546
|
-
// Start with width-based calculation
|
|
1547
|
-
finalWidth = maxAvailableWidth;
|
|
1548
|
-
finalHeight = (int) (finalWidth / ratio);
|
|
1549
|
-
|
|
1550
|
-
// If height exceeds available space, use height-based calculation
|
|
1551
|
-
if (finalHeight > maxAvailableHeight) {
|
|
1552
|
-
finalHeight = maxAvailableHeight;
|
|
1553
|
-
finalWidth = (int) (finalHeight * ratio);
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// Center the view
|
|
1557
|
-
finalX = (availableWidth - finalWidth) / 2;
|
|
1558
|
-
finalY = (availableHeight - finalHeight) / 2;
|
|
1559
|
-
|
|
1560
|
-
Log.d(TAG, "updatePreviewLayoutForAspectRatio: Auto-center mode - ratio=" + ratio +
|
|
1561
|
-
", calculated size=" + finalWidth + "x" + finalHeight +
|
|
1562
|
-
", available=" + availableWidth + "x" + availableHeight);
|
|
1929
|
+
Log.d(
|
|
1930
|
+
TAG,
|
|
1931
|
+
"setPreviewSize: Calculated aspect ratio from " +
|
|
1932
|
+
params.width +
|
|
1933
|
+
"x" +
|
|
1934
|
+
params.height +
|
|
1935
|
+
" = " +
|
|
1936
|
+
calculatedAspectRatio +
|
|
1937
|
+
" (normalized ratio=" +
|
|
1938
|
+
ratio +
|
|
1939
|
+
")"
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
sessionConfig = new CameraSessionConfiguration(
|
|
1944
|
+
sessionConfig.getDeviceId(),
|
|
1945
|
+
sessionConfig.getPosition(),
|
|
1946
|
+
params.leftMargin,
|
|
1947
|
+
params.topMargin,
|
|
1948
|
+
params.width,
|
|
1949
|
+
params.height,
|
|
1950
|
+
sessionConfig.getPaddingBottom(),
|
|
1951
|
+
sessionConfig.getToBack(),
|
|
1952
|
+
sessionConfig.getStoreToFile(),
|
|
1953
|
+
sessionConfig.getEnableOpacity(),
|
|
1954
|
+
sessionConfig.getEnableZoom(),
|
|
1955
|
+
sessionConfig.getDisableExifHeaderStripping(),
|
|
1956
|
+
sessionConfig.getDisableAudio(),
|
|
1957
|
+
sessionConfig.getZoomFactor(),
|
|
1958
|
+
calculatedAspectRatio,
|
|
1959
|
+
sessionConfig.getGridMode()
|
|
1960
|
+
);
|
|
1961
|
+
|
|
1962
|
+
// If aspect ratio changed due to size update, rebind camera
|
|
1963
|
+
if (
|
|
1964
|
+
isRunning &&
|
|
1965
|
+
!Objects.equals(currentAspectRatio, calculatedAspectRatio)
|
|
1966
|
+
) {
|
|
1967
|
+
Log.d(
|
|
1968
|
+
TAG,
|
|
1969
|
+
"setPreviewSize: Aspect ratio changed from " +
|
|
1970
|
+
currentAspectRatio +
|
|
1971
|
+
" to " +
|
|
1972
|
+
calculatedAspectRatio +
|
|
1973
|
+
", rebinding camera"
|
|
1974
|
+
);
|
|
1975
|
+
bindCameraUseCases();
|
|
1976
|
+
|
|
1977
|
+
// Wait for camera rebinding to complete, then call callback
|
|
1978
|
+
if (callback != null) {
|
|
1979
|
+
previewContainer.post(() -> previewContainer.post(callback));
|
|
1563
1980
|
}
|
|
1564
|
-
|
|
1565
|
-
//
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) currentParams;
|
|
1569
|
-
params.width = finalWidth;
|
|
1570
|
-
params.height = finalHeight;
|
|
1571
|
-
params.leftMargin = finalX;
|
|
1572
|
-
params.topMargin = finalY;
|
|
1573
|
-
previewContainer.setLayoutParams(params);
|
|
1574
|
-
previewContainer.requestLayout();
|
|
1575
|
-
Log.d(TAG, "updatePreviewLayoutForAspectRatio: Updated to " + finalWidth + "x" + finalHeight + " at (" + finalX + "," + finalY + ")");
|
|
1981
|
+
} else {
|
|
1982
|
+
// No camera rebinding needed, wait for layout to complete then call callback
|
|
1983
|
+
if (callback != null) {
|
|
1984
|
+
previewContainer.post(callback);
|
|
1576
1985
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1986
|
+
}
|
|
1987
|
+
} else {
|
|
1988
|
+
// No sessionConfig, just wait for layout then call callback
|
|
1989
|
+
if (callback != null) {
|
|
1990
|
+
previewContainer.post(callback);
|
|
1991
|
+
}
|
|
1579
1992
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1993
|
+
} else {
|
|
1994
|
+
Log.w(
|
|
1995
|
+
TAG,
|
|
1996
|
+
"setPreviewSize: Cannot set margins on layout params of type " +
|
|
1997
|
+
layoutParams.getClass().getSimpleName()
|
|
1998
|
+
);
|
|
1999
|
+
// Fallback: just set width and height if specified
|
|
2000
|
+
if (width > 0) layoutParams.width = width;
|
|
2001
|
+
if (height > 0) layoutParams.height = height;
|
|
2002
|
+
previewContainer.setLayoutParams(layoutParams);
|
|
2003
|
+
previewContainer.requestLayout();
|
|
2004
|
+
|
|
2005
|
+
// Wait for layout then call callback
|
|
2006
|
+
if (callback != null) {
|
|
2007
|
+
previewContainer.post(callback);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
1590
2012
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
2013
|
+
private void updatePreviewLayoutForAspectRatio(String aspectRatio) {
|
|
2014
|
+
updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
private void updatePreviewLayoutForAspectRatio(
|
|
2018
|
+
String aspectRatio,
|
|
2019
|
+
Float x,
|
|
2020
|
+
Float y
|
|
2021
|
+
) {
|
|
2022
|
+
if (previewContainer == null || aspectRatio == null) return;
|
|
2023
|
+
|
|
2024
|
+
// Parse aspect ratio
|
|
2025
|
+
String[] ratios = aspectRatio.split(":");
|
|
2026
|
+
if (ratios.length != 2) return;
|
|
2027
|
+
|
|
2028
|
+
try {
|
|
2029
|
+
// For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
|
|
2030
|
+
float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
|
|
2031
|
+
|
|
2032
|
+
// Get available space from webview dimensions
|
|
2033
|
+
int availableWidth = webView.getWidth();
|
|
2034
|
+
int availableHeight = webView.getHeight();
|
|
2035
|
+
|
|
2036
|
+
// Calculate position and size
|
|
2037
|
+
int finalX, finalY, finalWidth, finalHeight;
|
|
2038
|
+
|
|
2039
|
+
if (x != null && y != null) {
|
|
2040
|
+
// Account for WebView insets from edge-to-edge support
|
|
2041
|
+
int webViewTopInset = getWebViewTopInset();
|
|
2042
|
+
int webViewLeftInset = getWebViewLeftInset();
|
|
2043
|
+
|
|
2044
|
+
// Use provided coordinates with boundary checking, adjusted for insets
|
|
2045
|
+
finalX = Math.max(
|
|
2046
|
+
0,
|
|
2047
|
+
Math.min(x.intValue() + webViewLeftInset, availableWidth)
|
|
2048
|
+
);
|
|
2049
|
+
finalY = Math.max(
|
|
2050
|
+
0,
|
|
2051
|
+
Math.min(y.intValue() + webViewTopInset, availableHeight)
|
|
2052
|
+
);
|
|
2053
|
+
|
|
2054
|
+
// Calculate maximum available space from the given position
|
|
2055
|
+
int maxWidth = availableWidth - finalX;
|
|
2056
|
+
int maxHeight = availableHeight - finalY;
|
|
2057
|
+
|
|
2058
|
+
// Calculate optimal size while maintaining aspect ratio within available space
|
|
2059
|
+
finalWidth = maxWidth;
|
|
2060
|
+
finalHeight = (int) (maxWidth / ratio);
|
|
2061
|
+
|
|
2062
|
+
if (finalHeight > maxHeight) {
|
|
2063
|
+
// Height constraint is tighter, fit by height
|
|
2064
|
+
finalHeight = maxHeight;
|
|
2065
|
+
finalWidth = (int) (maxHeight * ratio);
|
|
1601
2066
|
}
|
|
1602
|
-
return 0;
|
|
1603
|
-
}
|
|
1604
2067
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2068
|
+
// Ensure final position stays within bounds
|
|
2069
|
+
finalX = Math.max(0, Math.min(finalX, availableWidth - finalWidth));
|
|
2070
|
+
finalY = Math.max(0, Math.min(finalY, availableHeight - finalHeight));
|
|
2071
|
+
} else {
|
|
2072
|
+
// Auto-center the view
|
|
2073
|
+
// Calculate size based on aspect ratio, using a reasonable base size
|
|
2074
|
+
// Use 80% of available space to ensure aspect ratio differences are visible
|
|
2075
|
+
int maxAvailableWidth = (int) (availableWidth * 0.8);
|
|
2076
|
+
int maxAvailableHeight = (int) (availableHeight * 0.8);
|
|
2077
|
+
|
|
2078
|
+
// Start with width-based calculation
|
|
2079
|
+
finalWidth = maxAvailableWidth;
|
|
2080
|
+
finalHeight = (int) (finalWidth / ratio);
|
|
2081
|
+
|
|
2082
|
+
// If height exceeds available space, use height-based calculation
|
|
2083
|
+
if (finalHeight > maxAvailableHeight) {
|
|
2084
|
+
finalHeight = maxAvailableHeight;
|
|
2085
|
+
finalWidth = (int) (finalHeight * ratio);
|
|
1615
2086
|
}
|
|
1616
|
-
|
|
2087
|
+
|
|
2088
|
+
// Center the view
|
|
2089
|
+
finalX = (availableWidth - finalWidth) / 2;
|
|
2090
|
+
finalY = (availableHeight - finalHeight) / 2;
|
|
2091
|
+
|
|
2092
|
+
Log.d(
|
|
2093
|
+
TAG,
|
|
2094
|
+
"updatePreviewLayoutForAspectRatio: Auto-center mode - ratio=" +
|
|
2095
|
+
ratio +
|
|
2096
|
+
", calculated size=" +
|
|
2097
|
+
finalWidth +
|
|
2098
|
+
"x" +
|
|
2099
|
+
finalHeight +
|
|
2100
|
+
", available=" +
|
|
2101
|
+
availableWidth +
|
|
2102
|
+
"x" +
|
|
2103
|
+
availableHeight
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
// Update layout params
|
|
2108
|
+
ViewGroup.LayoutParams currentParams = previewContainer.getLayoutParams();
|
|
2109
|
+
if (currentParams instanceof ViewGroup.MarginLayoutParams) {
|
|
2110
|
+
ViewGroup.MarginLayoutParams params =
|
|
2111
|
+
(ViewGroup.MarginLayoutParams) currentParams;
|
|
2112
|
+
params.width = finalWidth;
|
|
2113
|
+
params.height = finalHeight;
|
|
2114
|
+
params.leftMargin = finalX;
|
|
2115
|
+
params.topMargin = finalY;
|
|
2116
|
+
previewContainer.setLayoutParams(params);
|
|
2117
|
+
previewContainer.requestLayout();
|
|
2118
|
+
Log.d(
|
|
2119
|
+
TAG,
|
|
2120
|
+
"updatePreviewLayoutForAspectRatio: Updated to " +
|
|
2121
|
+
finalWidth +
|
|
2122
|
+
"x" +
|
|
2123
|
+
finalHeight +
|
|
2124
|
+
" at (" +
|
|
2125
|
+
finalX +
|
|
2126
|
+
"," +
|
|
2127
|
+
finalY +
|
|
2128
|
+
")"
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
} catch (NumberFormatException e) {
|
|
2132
|
+
Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
|
|
1617
2133
|
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
private void updatePreviewLayout() {
|
|
2137
|
+
if (previewContainer == null || sessionConfig == null) return;
|
|
1618
2138
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2139
|
+
String aspectRatio = sessionConfig.getAspectRatio();
|
|
2140
|
+
if (aspectRatio == null) return;
|
|
2141
|
+
|
|
2142
|
+
updatePreviewLayoutForAspectRatio(aspectRatio);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
private int getWebViewTopInset() {
|
|
2146
|
+
try {
|
|
2147
|
+
if (webView != null) {
|
|
2148
|
+
ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
|
|
2149
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
2150
|
+
return ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
|
|
1625
2151
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
2152
|
+
}
|
|
2153
|
+
} catch (Exception e) {
|
|
2154
|
+
Log.w(TAG, "Failed to get WebView top inset", e);
|
|
2155
|
+
}
|
|
2156
|
+
return 0;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
private int getWebViewLeftInset() {
|
|
2160
|
+
try {
|
|
2161
|
+
if (webView != null) {
|
|
2162
|
+
ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
|
|
1630
2163
|
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
// Remove insets to get original coordinates in DP
|
|
1634
|
-
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
1635
|
-
float pixelRatio = metrics.density;
|
|
1636
|
-
|
|
1637
|
-
int webViewTopInset = getWebViewTopInset();
|
|
1638
|
-
int webViewLeftInset = getWebViewLeftInset();
|
|
1639
|
-
|
|
1640
|
-
x = Math.max(0, (int) ((params.leftMargin - webViewLeftInset) / pixelRatio));
|
|
1641
|
-
y = Math.max(0, (int) ((params.topMargin - webViewTopInset) / pixelRatio));
|
|
1642
|
-
width = (int) (params.width / pixelRatio);
|
|
1643
|
-
height = (int) (params.height / pixelRatio);
|
|
2164
|
+
return ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
|
|
1644
2165
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
2166
|
+
}
|
|
2167
|
+
} catch (Exception e) {
|
|
2168
|
+
Log.w(TAG, "Failed to get WebView left inset", e);
|
|
2169
|
+
}
|
|
2170
|
+
return 0;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Get the current preview position and size in DP units (without insets)
|
|
2175
|
+
*/
|
|
2176
|
+
public int[] getCurrentPreviewBounds() {
|
|
2177
|
+
if (previewContainer == null) {
|
|
2178
|
+
return new int[] { 0, 0, 0, 0 }; // x, y, width, height
|
|
1647
2179
|
}
|
|
2180
|
+
|
|
2181
|
+
ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
|
|
2182
|
+
int x = 0, y = 0, width = 0, height = 0;
|
|
2183
|
+
|
|
2184
|
+
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
2185
|
+
ViewGroup.MarginLayoutParams params =
|
|
2186
|
+
(ViewGroup.MarginLayoutParams) layoutParams;
|
|
2187
|
+
|
|
2188
|
+
// Remove insets to get original coordinates in DP
|
|
2189
|
+
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
|
2190
|
+
float pixelRatio = metrics.density;
|
|
2191
|
+
|
|
2192
|
+
int webViewTopInset = getWebViewTopInset();
|
|
2193
|
+
int webViewLeftInset = getWebViewLeftInset();
|
|
2194
|
+
|
|
2195
|
+
x = Math.max(
|
|
2196
|
+
0,
|
|
2197
|
+
(int) ((params.leftMargin - webViewLeftInset) / pixelRatio)
|
|
2198
|
+
);
|
|
2199
|
+
y = Math.max(
|
|
2200
|
+
0,
|
|
2201
|
+
(int) ((params.topMargin - webViewTopInset) / pixelRatio)
|
|
2202
|
+
);
|
|
2203
|
+
width = (int) (params.width / pixelRatio);
|
|
2204
|
+
height = (int) (params.height / pixelRatio);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
return new int[] { x, y, width, height };
|
|
2208
|
+
}
|
|
1648
2209
|
}
|