2.6 强制暴露相机接口
This commit is contained in:
@ -11,7 +11,7 @@ android {
|
|||||||
minSdk 28
|
minSdk 28
|
||||||
targetSdk 28
|
targetSdk 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "2.15"
|
versionName "2.16"
|
||||||
|
|
||||||
// 1.0 IDATA广播模式处理
|
// 1.0 IDATA广播模式处理
|
||||||
// 1.1 霍尼韦尔的监听修改(扫描网站二维码跳出程序,监听失效,调整)、斑马PDA广播模式设置
|
// 1.1 霍尼韦尔的监听修改(扫描网站二维码跳出程序,监听失效,调整)、斑马PDA广播模式设置
|
||||||
@ -51,6 +51,7 @@ android {
|
|||||||
// 瑞芯适配器 接入 新的型号,使用的是 ttyS8;而不是ttyS1;并且只有一个接口。
|
// 瑞芯适配器 接入 新的型号,使用的是 ttyS8;而不是ttyS1;并且只有一个接口。
|
||||||
// 2.14 适配 AIFUU 陈安良:陆军特色中心医院
|
// 2.14 适配 AIFUU 陈安良:陆军特色中心医院
|
||||||
// 2.15 添加文件选择和相机拍照功能
|
// 2.15 添加文件选择和相机拍照功能
|
||||||
|
// 2.16 暴露了一个直接调用相机的方法,非http input调用会出现默认的弹出窗口进行选择(相机、文件)
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a'
|
abiFilters 'armeabi-v7a'
|
||||||
|
|||||||
@ -80,6 +80,7 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private ActionBar actionBar;
|
private ActionBar actionBar;
|
||||||
private FileChooserHelper fileChooserHelper;
|
private FileChooserHelper fileChooserHelper;
|
||||||
|
private LocalAddressUtil localAddressUtil;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@ -236,7 +237,8 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
webView.addJavascriptInterface(settingEngine, "NetworkSettingEngine");
|
webView.addJavascriptInterface(settingEngine, "NetworkSettingEngine");
|
||||||
//重新加载页面
|
//重新加载页面
|
||||||
webView.addJavascriptInterface(this, "View");
|
webView.addJavascriptInterface(this, "View");
|
||||||
webView.addJavascriptInterface(new LocalAddressUtil(this, this, webView), "Localpda");
|
localAddressUtil = new LocalAddressUtil(this, this, webView);
|
||||||
|
webView.addJavascriptInterface(localAddressUtil, "Localpda");
|
||||||
webView.loadUrl(url());
|
webView.loadUrl(url());
|
||||||
// StatusBarUtil.transparencyBar( this); // 设置全部透明,需要在页面设置一个参数进行布局的样式跳转,不同的手机端,状态栏高度不一样(apk设置状态栏高度无效,这个是安卓9的一个bug),所以在此采取隐藏状态栏
|
// StatusBarUtil.transparencyBar( this); // 设置全部透明,需要在页面设置一个参数进行布局的样式跳转,不同的手机端,状态栏高度不一样(apk设置状态栏高度无效,这个是安卓9的一个bug),所以在此采取隐藏状态栏
|
||||||
if (hideBar == 1) {
|
if (hideBar == 1) {
|
||||||
@ -583,7 +585,8 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (!fileChooserHelper.onActivityResult(requestCode, resultCode, data)) {
|
if (!fileChooserHelper.onActivityResult(requestCode, resultCode, data)
|
||||||
|
&& (localAddressUtil == null || !localAddressUtil.onActivityResult(requestCode, resultCode, data))) {
|
||||||
// 如果不是文件选择器的结果,可以在这里处理其他结果
|
// 如果不是文件选择器的结果,可以在这里处理其他结果
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -591,7 +594,8 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (!fileChooserHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
if (!fileChooserHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
&& (localAddressUtil == null || !localAddressUtil.onRequestPermissionsResult(requestCode, permissions, grantResults))) {
|
||||||
// 如果不是文件选择器的权限请求,可以在这里处理其他权限请求
|
// 如果不是文件选择器的权限请求,可以在这里处理其他权限请求
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,47 @@
|
|||||||
package chaoran.business.utils;
|
package chaoran.business.utils;
|
||||||
|
|
||||||
import static androidx.core.content.ContextCompat.getSystemService;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
import android.net.wifi.WifiInfo;
|
import android.net.wifi.WifiInfo;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import chaoran.business.BuildConfig;
|
import chaoran.business.BuildConfig;
|
||||||
@ -38,6 +58,19 @@ public class LocalAddressUtil {
|
|||||||
|
|
||||||
private int[] heights;
|
private int[] heights;
|
||||||
|
|
||||||
|
private static final String TAG = "LocalAddressUtil";
|
||||||
|
private static final int REQUEST_CODE_NATIVE_CAMERA = 31002;
|
||||||
|
private static final int REQUEST_CODE_PERMISSION_CAMERA = 31001;
|
||||||
|
private static final String DEFAULT_CAMERA_CALLBACK = "onNativeCameraResult";
|
||||||
|
|
||||||
|
// Base64 settings for camera callback
|
||||||
|
private static final int CAMERA_IMAGE_MAX_DIMENSION = 1280;
|
||||||
|
private static final int CAMERA_IMAGE_JPEG_QUALITY = 80;
|
||||||
|
private static final String CAMERA_DATA_URL_PREFIX = "data:image/jpeg;base64,";
|
||||||
|
|
||||||
|
private Uri nativeCameraPhotoUri;
|
||||||
|
private String pendingCameraCallback = null;
|
||||||
|
|
||||||
public LocalAddressUtil(Context context, Activity activity, View view) {
|
public LocalAddressUtil(Context context, Activity activity, View view) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
@ -45,6 +78,221 @@ public class LocalAddressUtil {
|
|||||||
this.heights = StatusBarUtil.getStatusBarHeight(context);
|
this.heights = StatusBarUtil.getStatusBarHeight(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接调用原生相机(默认回调:window.onNativeCameraResult(base64OrDataUrl, error))
|
||||||
|
*/
|
||||||
|
@SuppressLint("JavascriptInterface")
|
||||||
|
@JavascriptInterface
|
||||||
|
public void openNativeCamera() {
|
||||||
|
openNativeCamera(DEFAULT_CAMERA_CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接调用原生相机(自定义回调函数名:window[callback](base64OrDataUrl, error))
|
||||||
|
* @param callback JS 回调函数名(不需要传 window. 前缀)
|
||||||
|
*/
|
||||||
|
@SuppressLint("JavascriptInterface")
|
||||||
|
@JavascriptInterface
|
||||||
|
public void openNativeCamera(String callback) {
|
||||||
|
this.pendingCameraCallback = (callback == null || callback.trim().isEmpty())
|
||||||
|
? DEFAULT_CAMERA_CALLBACK
|
||||||
|
: callback.trim();
|
||||||
|
|
||||||
|
if (!checkCameraPermission()) {
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{android.Manifest.permission.CAMERA},
|
||||||
|
REQUEST_CODE_PERMISSION_CAMERA);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent cameraIntent = createNativeCameraIntent();
|
||||||
|
if (cameraIntent == null) {
|
||||||
|
dispatchCameraResultToJs(null, "create_intent_failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(cameraIntent, REQUEST_CODE_NATIVE_CAMERA);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "启动相机失败", e);
|
||||||
|
dispatchCameraResultToJs(null, "start_failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由宿主 Activity 转发调用
|
||||||
|
*/
|
||||||
|
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode != REQUEST_CODE_NATIVE_CAMERA) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
String dataUrl = buildCameraPhotoDataUrl(nativeCameraPhotoUri);
|
||||||
|
if (dataUrl == null || dataUrl.trim().isEmpty()) {
|
||||||
|
dispatchCameraResultToJs(null, "encode_failed");
|
||||||
|
} else {
|
||||||
|
dispatchCameraResultToJs(dataUrl, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatchCameraResultToJs(null, "cancelled");
|
||||||
|
}
|
||||||
|
nativeCameraPhotoUri = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 由宿主 Activity 转发调用
|
||||||
|
*/
|
||||||
|
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
if (requestCode != REQUEST_CODE_PERMISSION_CAMERA) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean granted = grantResults != null && grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
|
if (granted) {
|
||||||
|
openNativeCamera(pendingCameraCallback);
|
||||||
|
} else {
|
||||||
|
dispatchCameraResultToJs(null, "permission_denied");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCameraPermission() {
|
||||||
|
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent createNativeCameraIntent() {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
if (cameraIntent.resolveActivity(activity.getPackageManager()) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
File photoFile = createImageFile();
|
||||||
|
if (photoFile == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
nativeCameraPhotoUri = FileProvider.getUriForFile(
|
||||||
|
activity,
|
||||||
|
activity.getPackageName() + ".fileprovider",
|
||||||
|
photoFile
|
||||||
|
);
|
||||||
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, nativeCameraPhotoUri);
|
||||||
|
cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
return cameraIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createImageFile() {
|
||||||
|
try {
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||||
|
String imageFileName = "JPEG_" + timeStamp + "_";
|
||||||
|
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||||
|
if (storageDir != null && !storageDir.exists()) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
storageDir.mkdirs();
|
||||||
|
}
|
||||||
|
return File.createTempFile(imageFileName, ".jpg", storageDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "创建图片文件失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将拍照结果转为 dataUrl(base64)
|
||||||
|
*/
|
||||||
|
private String buildCameraPhotoDataUrl(Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeStream(inputStream, null, options);
|
||||||
|
int srcW = options.outWidth;
|
||||||
|
int srcH = options.outHeight;
|
||||||
|
|
||||||
|
safeClose(inputStream);
|
||||||
|
inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.inSampleSize = calculateInSampleSize(srcW, srcH, CAMERA_IMAGE_MAX_DIMENSION);
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, CAMERA_IMAGE_JPEG_QUALITY, baos);
|
||||||
|
byte[] bytes = baos.toByteArray();
|
||||||
|
|
||||||
|
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||||
|
return CAMERA_DATA_URL_PREFIX + base64;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "图片转base64失败", e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
safeClose(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateInSampleSize(int srcW, int srcH, int maxDim) {
|
||||||
|
if (srcW <= 0 || srcH <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int maxSrc = Math.max(srcW, srcH);
|
||||||
|
if (maxSrc <= maxDim) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int inSampleSize = 1;
|
||||||
|
while (maxSrc / inSampleSize > maxDim) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
}
|
||||||
|
return Math.max(1, inSampleSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeClose(InputStream is) {
|
||||||
|
try {
|
||||||
|
if (is != null) {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchCameraResultToJs(String dataUrl, String error) {
|
||||||
|
if (!(view instanceof WebView)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WebView webView = (WebView) view;
|
||||||
|
String cb = (pendingCameraCallback == null || pendingCameraCallback.trim().isEmpty())
|
||||||
|
? DEFAULT_CAMERA_CALLBACK
|
||||||
|
: pendingCameraCallback.trim();
|
||||||
|
|
||||||
|
String js = "try{" +
|
||||||
|
"var cb=window[" + JSONObject.quote(cb) + "];" +
|
||||||
|
"if(typeof cb==='function'){cb(" + JSONObject.quote(dataUrl) + "," + JSONObject.quote(error) + ");}" +
|
||||||
|
"}catch(e){}";
|
||||||
|
|
||||||
|
webView.post(() -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
webView.evaluateJavascript(js, null);
|
||||||
|
} else {
|
||||||
|
webView.loadUrl("javascript:" + js);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("JavascriptInterface")
|
@SuppressLint("JavascriptInterface")
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
public String getIpAddress() {
|
public String getIpAddress() {
|
||||||
|
|||||||
Reference in New Issue
Block a user