integrate qr code scanner. temporarily break omemo activity scan
This commit is contained in:
parent
6652135746
commit
dfb4e4eb46
|
@ -14,6 +14,8 @@
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.READ_PHONE_STATE"
|
android:name="android.permission.READ_PHONE_STATE"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
@ -56,10 +58,14 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.ScanActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/ConversationsTheme.FullScreen"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.UriHandlerActivity"
|
android:name=".ui.UriHandlerActivity"
|
||||||
android:label="@string/title_activity_start_conversation"
|
android:label="@string/title_activity_start_conversation">
|
||||||
android:launchMode="singleTop">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
|
|
@ -252,6 +252,7 @@ public class ConversationActivity extends XmppActivity implements OnConversation
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processViewIntent(Intent intent) {
|
private boolean processViewIntent(Intent intent) {
|
||||||
|
Log.d(Config.LOGTAG,"process view intent");
|
||||||
String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
|
String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
|
||||||
Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
|
Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
|
||||||
if (conversation == null) {
|
if (conversation == null) {
|
||||||
|
@ -262,9 +263,13 @@ public class ConversationActivity extends XmppActivity implements OnConversation
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
||||||
|
UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
||||||
Log.d(Config.LOGTAG,"on activity result");
|
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
handlePositiveActivityResult(requestCode, data);
|
handlePositiveActivityResult(requestCode, data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -308,7 +313,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation
|
||||||
this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
|
this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
|
||||||
this.initializeFragments();
|
this.initializeFragments();
|
||||||
this.invalidateActionBarTitle();
|
this.invalidateActionBarTitle();
|
||||||
final Intent intent = getIntent();
|
final Intent intent;
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
intent = getIntent();
|
||||||
|
} else {
|
||||||
|
intent = savedInstanceState.getParcelable("intent");
|
||||||
|
}
|
||||||
if (isViewIntent(intent)) {
|
if (isViewIntent(intent)) {
|
||||||
pendingViewIntent.push(intent);
|
pendingViewIntent.push(intent);
|
||||||
setIntent(createLauncherIntent(this));
|
setIntent(createLauncherIntent(this));
|
||||||
|
@ -377,6 +387,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
savedInstanceState.putParcelable("intent", getIntent());
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
final int theme = findTheme();
|
final int theme = findTheme();
|
||||||
|
|
|
@ -152,10 +152,16 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle bundle) {
|
public void onSaveInstanceState(Bundle bundle) {
|
||||||
super.onSaveInstanceState(bundle);
|
super.onSaveInstanceState(bundle);
|
||||||
bundle.putParcelable(STATE_SCROLL_POSITION,getScrollState());
|
ScrollState scrollState = getScrollState();
|
||||||
|
if (scrollState != null) {
|
||||||
|
bundle.putParcelable(STATE_SCROLL_POSITION, scrollState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScrollState getScrollState() {
|
private ScrollState getScrollState() {
|
||||||
|
if (this.binding == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
int position = this.binding.list.getFirstVisiblePosition();
|
int position = this.binding.list.getFirstVisiblePosition();
|
||||||
final View view = this.binding.list.getChildAt(0);
|
final View view = this.binding.list.getChildAt(0);
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
|
|
|
@ -25,9 +25,6 @@ import eu.siacs.conversations.databinding.ContactKeyBinding;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.utils.zxing.IntentIntegrator;
|
|
||||||
import eu.siacs.conversations.utils.zxing.IntentResult;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class OmemoActivity extends XmppActivity {
|
public abstract class OmemoActivity extends XmppActivity {
|
||||||
|
|
||||||
|
@ -76,7 +73,7 @@ public abstract class OmemoActivity extends XmppActivity {
|
||||||
copyOmemoFingerprint(mSelectedFingerprint);
|
copyOmemoFingerprint(mSelectedFingerprint);
|
||||||
break;
|
break;
|
||||||
case R.id.verify_scan:
|
case R.id.verify_scan:
|
||||||
new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
|
//new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -84,7 +81,7 @@ public abstract class OmemoActivity extends XmppActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
/*IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
if (scanResult != null && scanResult.getFormatName() != null) {
|
||||||
String data = scanResult.getContents();
|
String data = scanResult.getContents();
|
||||||
XmppUri uri = new XmppUri(data);
|
XmppUri uri = new XmppUri(data);
|
||||||
|
@ -93,7 +90,7 @@ public abstract class OmemoActivity extends XmppActivity {
|
||||||
} else {
|
} else {
|
||||||
this.mPendingFingerprintVerificationUri =uri;
|
this.mPendingFingerprintVerificationUri =uri;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void processFingerprintVerification(XmppUri uri);
|
protected abstract void processFingerprintVerification(XmppUri uri);
|
||||||
|
|
292
src/main/java/eu/siacs/conversations/ui/ScanActivity.java
Normal file
292
src/main/java/eu/siacs/conversations/ui/ScanActivity.java
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.DecodeHintType;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.ReaderException;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.ResultPointCallback;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.hardware.Camera.CameraInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.view.TextureView.SurfaceTextureListener;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.ui.service.CameraManager;
|
||||||
|
import eu.siacs.conversations.ui.widget.ScannerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andreas Schildbach
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public final class ScanActivity extends Activity implements SurfaceTextureListener, ActivityCompat.OnRequestPermissionsResultCallback {
|
||||||
|
public static final String INTENT_EXTRA_RESULT = "result";
|
||||||
|
|
||||||
|
private static final long VIBRATE_DURATION = 50L;
|
||||||
|
private static final long AUTO_FOCUS_INTERVAL_MS = 2500L;
|
||||||
|
private static boolean DISABLE_CONTINUOUS_AUTOFOCUS = Build.MODEL.equals("GT-I9100") // Galaxy S2
|
||||||
|
|| Build.MODEL.equals("SGH-T989") // Galaxy S2
|
||||||
|
|| Build.MODEL.equals("SGH-T989D") // Galaxy S2 X
|
||||||
|
|| Build.MODEL.equals("SAMSUNG-SGH-I727") // Galaxy S2 Skyrocket
|
||||||
|
|| Build.MODEL.equals("GT-I9300") // Galaxy S3
|
||||||
|
|| Build.MODEL.equals("GT-N7000"); // Galaxy Note
|
||||||
|
private final CameraManager cameraManager = new CameraManager();
|
||||||
|
private ScannerView scannerView;
|
||||||
|
private TextureView previewView;
|
||||||
|
private volatile boolean surfaceCreated = false;
|
||||||
|
private Vibrator vibrator;
|
||||||
|
private HandlerThread cameraThread;
|
||||||
|
private volatile Handler cameraHandler;
|
||||||
|
private final Runnable closeRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cameraHandler.removeCallbacksAndMessages(null);
|
||||||
|
cameraManager.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Runnable fetchAndDecodeRunnable = new Runnable() {
|
||||||
|
private final QRCodeReader reader = new QRCodeReader();
|
||||||
|
private final Map<DecodeHintType, Object> hints = new EnumMap<DecodeHintType, Object>(DecodeHintType.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
cameraManager.requestPreviewFrame((data, camera) -> decode(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decode(final byte[] data) {
|
||||||
|
final PlanarYUVLuminanceSource source = cameraManager.buildLuminanceSource(data);
|
||||||
|
final BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
try {
|
||||||
|
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, (ResultPointCallback) dot -> runOnUiThread(() -> scannerView.addDot(dot)));
|
||||||
|
final Result scanResult = reader.decode(bitmap, hints);
|
||||||
|
|
||||||
|
runOnUiThread(() -> handleResult(scanResult));
|
||||||
|
} catch (final ReaderException x) {
|
||||||
|
// retry
|
||||||
|
cameraHandler.post(fetchAndDecodeRunnable);
|
||||||
|
} finally {
|
||||||
|
reader.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Runnable openRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final Camera camera = cameraManager.open(previewView, displayRotation(), !DISABLE_CONTINUOUS_AUTOFOCUS);
|
||||||
|
|
||||||
|
final Rect framingRect = cameraManager.getFrame();
|
||||||
|
final RectF framingRectInPreview = new RectF(cameraManager.getFramePreview());
|
||||||
|
framingRectInPreview.offsetTo(0, 0);
|
||||||
|
final boolean cameraFlip = cameraManager.getFacing() == CameraInfo.CAMERA_FACING_FRONT;
|
||||||
|
final int cameraRotation = cameraManager.getOrientation();
|
||||||
|
|
||||||
|
runOnUiThread(() -> scannerView.setFraming(framingRect, framingRectInPreview, displayRotation(), cameraRotation, cameraFlip));
|
||||||
|
|
||||||
|
final String focusMode = camera.getParameters().getFocusMode();
|
||||||
|
final boolean nonContinuousAutoFocus = Camera.Parameters.FOCUS_MODE_AUTO.equals(focusMode)
|
||||||
|
|| Camera.Parameters.FOCUS_MODE_MACRO.equals(focusMode);
|
||||||
|
|
||||||
|
if (nonContinuousAutoFocus)
|
||||||
|
cameraHandler.post(new AutoFocusRunnable(camera));
|
||||||
|
|
||||||
|
cameraHandler.post(fetchAndDecodeRunnable);
|
||||||
|
} catch (final Exception x) {
|
||||||
|
Log.d(Config.LOGTAG, "problem opening camera", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int displayRotation() {
|
||||||
|
final int rotation = getWindowManager().getDefaultDisplay().getRotation();
|
||||||
|
if (rotation == Surface.ROTATION_0)
|
||||||
|
return 0;
|
||||||
|
else if (rotation == Surface.ROTATION_90)
|
||||||
|
return 90;
|
||||||
|
else if (rotation == Surface.ROTATION_180)
|
||||||
|
return 180;
|
||||||
|
else if (rotation == Surface.ROTATION_270)
|
||||||
|
return 270;
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("rotation: " + rotation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_scan);
|
||||||
|
scannerView = findViewById(R.id.scan_activity_mask);
|
||||||
|
previewView = findViewById(R.id.scan_activity_preview);
|
||||||
|
previewView.setSurfaceTextureListener(this);
|
||||||
|
|
||||||
|
cameraThread = new HandlerThread("cameraThread", Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
|
cameraThread.start();
|
||||||
|
cameraHandler = new Handler(cameraThread.getLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
maybeOpenCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
cameraHandler.post(closeRunnable);
|
||||||
|
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
// cancel background thread
|
||||||
|
cameraHandler.removeCallbacksAndMessages(null);
|
||||||
|
cameraThread.quit();
|
||||||
|
|
||||||
|
previewView.setSurfaceTextureListener(null);
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeOpenCamera() {
|
||||||
|
if (surfaceCreated && ContextCompat.checkSelfPermission(this,
|
||||||
|
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)
|
||||||
|
cameraHandler.post(openRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
|
||||||
|
surfaceCreated = true;
|
||||||
|
maybeOpenCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {
|
||||||
|
surfaceCreated = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToWindow() {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
scannerView.setVisibility(View.GONE);
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
postFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_FOCUS:
|
||||||
|
case KeyEvent.KEYCODE_CAMERA:
|
||||||
|
// don't launch camera app
|
||||||
|
return true;
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
cameraHandler.post(() -> cameraManager.setTorch(keyCode == KeyEvent.KEYCODE_VOLUME_UP));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleResult(final Result scanResult) {
|
||||||
|
vibrator.vibrate(VIBRATE_DURATION);
|
||||||
|
|
||||||
|
scannerView.setIsResult(true);
|
||||||
|
|
||||||
|
final Intent result = new Intent();
|
||||||
|
result.putExtra(INTENT_EXTRA_RESULT, scanResult.getText());
|
||||||
|
setResult(RESULT_OK, result);
|
||||||
|
postFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postFinish() {
|
||||||
|
new Handler().postDelayed(() -> finish(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class AutoFocusRunnable implements Runnable {
|
||||||
|
private final Camera camera;
|
||||||
|
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAutoFocus(final boolean success, final Camera camera) {
|
||||||
|
// schedule again
|
||||||
|
cameraHandler.postDelayed(AutoFocusRunnable.this, AUTO_FOCUS_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public AutoFocusRunnable(final Camera camera) {
|
||||||
|
this.camera = camera;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
camera.autoFocus(autoFocusCallback);
|
||||||
|
} catch (final Exception x) {
|
||||||
|
Log.d(Config.LOGTAG, "problem with auto-focus, will not schedule again", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,12 +36,10 @@ import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.utils.zxing.IntentIntegrator;
|
|
||||||
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
|
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
import static android.databinding.DataBindingUtil.inflate;
|
|
||||||
|
|
||||||
public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
|
public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
|
||||||
private List<Jid> contactJids;
|
private List<Jid> contactJids;
|
||||||
|
@ -135,7 +133,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
|
||||||
if (hasPendingKeyFetches()) {
|
if (hasPendingKeyFetches()) {
|
||||||
Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
|
//new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,60 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.support.v7.app.AppCompatActivity ;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v13.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.utils.zxing.IntentIntegrator;
|
|
||||||
import eu.siacs.conversations.utils.zxing.IntentResult;
|
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
public class UriHandlerActivity extends AppCompatActivity {
|
public class UriHandlerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
public static final String ACTION_SCAN_QR_CODE = "scan_qr_code";
|
public static final String ACTION_SCAN_QR_CODE = "scan_qr_code";
|
||||||
|
private static final int REQUEST_SCAN_QR_CODE = 0x1234;
|
||||||
|
private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789;
|
||||||
|
|
||||||
|
private boolean handled = false;
|
||||||
|
|
||||||
|
public static void scan(Activity activity) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Intent intent = new Intent(activity, UriHandlerActivity.class);
|
||||||
|
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSIONS_TO_SCAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onRequestPermissionResult(Activity activity, int requestCode, int[] grantResults) {
|
||||||
|
if (requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (grantResults.length > 0) {
|
||||||
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
scan(activity);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity, R.string.qr_code_scanner_needs_access_to_camera, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled",false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
|
@ -26,6 +62,12 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
savedInstanceState.putBoolean("handled", this.handled);
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
|
@ -34,7 +76,7 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
private void handleUri(Uri uri) {
|
private void handleUri(Uri uri) {
|
||||||
final Intent intent;
|
final Intent intent;
|
||||||
final XmppUri xmppUri = new XmppUri(uri);
|
final XmppUri xmppUri = new XmppUri(uri);
|
||||||
final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids();
|
final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(); //TODO only look at enabled accounts
|
||||||
|
|
||||||
if (accounts.size() == 0) {
|
if (accounts.size() == 0) {
|
||||||
intent = new Intent(getApplicationContext(), WelcomeActivity.class);
|
intent = new Intent(getApplicationContext(), WelcomeActivity.class);
|
||||||
|
@ -73,18 +115,24 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIntent(Intent data) {
|
private void handleIntent(Intent data) {
|
||||||
|
if (handled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data == null || data.getAction() == null) {
|
if (data == null || data.getAction() == null) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
|
||||||
switch (data.getAction()) {
|
switch (data.getAction()) {
|
||||||
case Intent.ACTION_VIEW:
|
case Intent.ACTION_VIEW:
|
||||||
case Intent.ACTION_SENDTO:
|
case Intent.ACTION_SENDTO:
|
||||||
handleUri(data.getData());
|
handleUri(data.getData());
|
||||||
break;
|
break;
|
||||||
case ACTION_SCAN_QR_CODE:
|
case ACTION_SCAN_QR_CODE:
|
||||||
new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC", "QR_CODE"));
|
Intent intent = new Intent(this, ScanActivity.class);
|
||||||
|
startActivityForResult(intent, REQUEST_SCAN_QR_CODE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,23 +141,14 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
|
|
||||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
|
||||||
|
|
||||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
|
||||||
String data = scanResult.getContents();
|
|
||||||
handleUri(Uri.parse(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finish();
|
|
||||||
super.onActivityResult(requestCode, requestCode, intent);
|
super.onActivityResult(requestCode, requestCode, intent);
|
||||||
|
if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) {
|
||||||
|
String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
|
||||||
|
if (result != null) {
|
||||||
|
Uri uri = Uri.parse(result);
|
||||||
|
handleUri(uri);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public static void scan(Activity activity) {
|
finish();
|
||||||
Intent intent = new Intent(activity, UriHandlerActivity.class);
|
|
||||||
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.ui.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.hardware.Camera;
|
||||||
|
import android.hardware.Camera.CameraInfo;
|
||||||
|
import android.hardware.Camera.PreviewCallback;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.TextureView;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andreas Schildbach
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public final class CameraManager {
|
||||||
|
private static final int MIN_FRAME_SIZE = 240;
|
||||||
|
private static final int MAX_FRAME_SIZE = 600;
|
||||||
|
private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
|
||||||
|
private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
|
||||||
|
|
||||||
|
private Camera camera;
|
||||||
|
private CameraInfo cameraInfo = new CameraInfo();
|
||||||
|
private Camera.Size cameraResolution;
|
||||||
|
private Rect frame;
|
||||||
|
private RectF framePreview;
|
||||||
|
|
||||||
|
public Rect getFrame() {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectF getFramePreview() {
|
||||||
|
return framePreview;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFacing() {
|
||||||
|
return cameraInfo.facing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOrientation() {
|
||||||
|
return cameraInfo.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Camera open(final TextureView textureView, final int displayOrientation, final boolean continuousAutoFocus)
|
||||||
|
throws IOException {
|
||||||
|
final int cameraId = determineCameraId();
|
||||||
|
Camera.getCameraInfo(cameraId, cameraInfo);
|
||||||
|
|
||||||
|
camera = Camera.open(cameraId);
|
||||||
|
|
||||||
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
|
||||||
|
camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360);
|
||||||
|
else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
|
||||||
|
camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360);
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("facing: " + cameraInfo.facing);
|
||||||
|
|
||||||
|
camera.setPreviewTexture(textureView.getSurfaceTexture());
|
||||||
|
|
||||||
|
final Camera.Parameters parameters = camera.getParameters();
|
||||||
|
|
||||||
|
cameraResolution = findBestPreviewSizeValue(parameters, textureView.getWidth(), textureView.getHeight());
|
||||||
|
|
||||||
|
final int width = textureView.getWidth();
|
||||||
|
final int height = textureView.getHeight();
|
||||||
|
|
||||||
|
final int rawSize = Math.min(width * 2 / 3, height * 2 / 3);
|
||||||
|
final int frameSize = Math.max(MIN_FRAME_SIZE, Math.min(MAX_FRAME_SIZE, rawSize));
|
||||||
|
|
||||||
|
final int leftOffset = (width - frameSize) / 2;
|
||||||
|
final int topOffset = (height - frameSize) / 2;
|
||||||
|
frame = new Rect(leftOffset, topOffset, leftOffset + frameSize, topOffset + frameSize);
|
||||||
|
framePreview = new RectF(frame.left * cameraResolution.width / width,
|
||||||
|
frame.top * cameraResolution.height / height, frame.right * cameraResolution.width / width,
|
||||||
|
frame.bottom * cameraResolution.height / height);
|
||||||
|
|
||||||
|
final String savedParameters = parameters == null ? null : parameters.flatten();
|
||||||
|
|
||||||
|
try {
|
||||||
|
setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
|
||||||
|
} catch (final RuntimeException x) {
|
||||||
|
if (savedParameters != null) {
|
||||||
|
final Camera.Parameters parameters2 = camera.getParameters();
|
||||||
|
parameters2.unflatten(savedParameters);
|
||||||
|
try {
|
||||||
|
camera.setParameters(parameters2);
|
||||||
|
setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
|
||||||
|
} catch (final RuntimeException x2) {
|
||||||
|
Log.d(Config.LOGTAG,"problem setting camera parameters", x2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
camera.startPreview();
|
||||||
|
return camera;
|
||||||
|
} catch (final RuntimeException x) {
|
||||||
|
Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x);
|
||||||
|
camera.release();
|
||||||
|
throw x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int determineCameraId() {
|
||||||
|
final int cameraCount = Camera.getNumberOfCameras();
|
||||||
|
final CameraInfo cameraInfo = new CameraInfo();
|
||||||
|
|
||||||
|
// prefer back-facing camera
|
||||||
|
for (int i = 0; i < cameraCount; i++) {
|
||||||
|
Camera.getCameraInfo(i, cameraInfo);
|
||||||
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to front-facing camera
|
||||||
|
for (int i = 0; i < cameraCount; i++) {
|
||||||
|
Camera.getCameraInfo(i, cameraInfo);
|
||||||
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (camera != null) {
|
||||||
|
try {
|
||||||
|
camera.stopPreview();
|
||||||
|
} catch (final RuntimeException x) {
|
||||||
|
Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x);
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<Camera.Size> numPixelComparator = new Comparator<Camera.Size>() {
|
||||||
|
@Override
|
||||||
|
public int compare(final Camera.Size size1, final Camera.Size size2) {
|
||||||
|
final int pixels1 = size1.height * size1.width;
|
||||||
|
final int pixels2 = size2.height * size2.width;
|
||||||
|
|
||||||
|
if (pixels1 < pixels2)
|
||||||
|
return 1;
|
||||||
|
else if (pixels1 > pixels2)
|
||||||
|
return -1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, int width, int height) {
|
||||||
|
if (height > width) {
|
||||||
|
final int temp = width;
|
||||||
|
width = height;
|
||||||
|
height = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float screenAspectRatio = (float) width / (float) height;
|
||||||
|
|
||||||
|
final List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
|
||||||
|
if (rawSupportedSizes == null)
|
||||||
|
return parameters.getPreviewSize();
|
||||||
|
|
||||||
|
// sort by size, descending
|
||||||
|
final List<Camera.Size> supportedPreviewSizes = new ArrayList<Camera.Size>(rawSupportedSizes);
|
||||||
|
Collections.sort(supportedPreviewSizes, numPixelComparator);
|
||||||
|
|
||||||
|
Camera.Size bestSize = null;
|
||||||
|
float diff = Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
for (final Camera.Size supportedPreviewSize : supportedPreviewSizes) {
|
||||||
|
final int realWidth = supportedPreviewSize.width;
|
||||||
|
final int realHeight = supportedPreviewSize.height;
|
||||||
|
final int realPixels = realWidth * realHeight;
|
||||||
|
if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
final boolean isCandidatePortrait = realWidth < realHeight;
|
||||||
|
final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
|
||||||
|
final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
|
||||||
|
if (maybeFlippedWidth == width && maybeFlippedHeight == height)
|
||||||
|
return supportedPreviewSize;
|
||||||
|
|
||||||
|
final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
|
||||||
|
final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
|
||||||
|
if (newDiff < diff) {
|
||||||
|
bestSize = supportedPreviewSize;
|
||||||
|
diff = newDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestSize != null)
|
||||||
|
return bestSize;
|
||||||
|
else
|
||||||
|
return parameters.getPreviewSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution,
|
||||||
|
final boolean continuousAutoFocus) {
|
||||||
|
final Camera.Parameters parameters = camera.getParameters();
|
||||||
|
if (parameters == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||||
|
final String focusMode = continuousAutoFocus
|
||||||
|
? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
|
||||||
|
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO,
|
||||||
|
Camera.Parameters.FOCUS_MODE_MACRO)
|
||||||
|
: findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO);
|
||||||
|
if (focusMode != null)
|
||||||
|
parameters.setFocusMode(focusMode);
|
||||||
|
|
||||||
|
parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
|
||||||
|
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestPreviewFrame(final PreviewCallback callback) {
|
||||||
|
try {
|
||||||
|
camera.setOneShotPreviewCallback(callback);
|
||||||
|
} catch (final RuntimeException x) {
|
||||||
|
Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) {
|
||||||
|
return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height,
|
||||||
|
(int) framePreview.left, (int) framePreview.top, (int) framePreview.width(),
|
||||||
|
(int) framePreview.height(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTorch(final boolean enabled) {
|
||||||
|
if (enabled != getTorchEnabled(camera))
|
||||||
|
setTorchEnabled(camera, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getTorchEnabled(final Camera camera) {
|
||||||
|
final Camera.Parameters parameters = camera.getParameters();
|
||||||
|
if (parameters != null) {
|
||||||
|
final String flashMode = camera.getParameters().getFlashMode();
|
||||||
|
return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)
|
||||||
|
|| Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setTorchEnabled(final Camera camera, final boolean enabled) {
|
||||||
|
final Camera.Parameters parameters = camera.getParameters();
|
||||||
|
|
||||||
|
final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
|
||||||
|
if (supportedFlashModes != null) {
|
||||||
|
final String flashMode;
|
||||||
|
if (enabled)
|
||||||
|
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH,
|
||||||
|
Camera.Parameters.FLASH_MODE_ON);
|
||||||
|
else
|
||||||
|
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
|
||||||
|
|
||||||
|
if (flashMode != null) {
|
||||||
|
camera.cancelAutoFocus(); // autofocus can cause conflict
|
||||||
|
|
||||||
|
parameters.setFlashMode(flashMode);
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findValue(final Collection<String> values, final String... valuesToFind) {
|
||||||
|
for (final String valueToFind : valuesToFind)
|
||||||
|
if (values.contains(valueToFind))
|
||||||
|
return valueToFind;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
157
src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java
Normal file
157
src/main/java/eu/siacs/conversations/ui/widget/ScannerView.java
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.ui.widget;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.zxing.ResultPoint;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Matrix.ScaleToFit;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andreas Schildbach
|
||||||
|
*/
|
||||||
|
public class ScannerView extends View {
|
||||||
|
private static final long LASER_ANIMATION_DELAY_MS = 100l;
|
||||||
|
private static final int DOT_OPACITY = 0xa0;
|
||||||
|
private static final int DOT_TTL_MS = 500;
|
||||||
|
|
||||||
|
private final Paint maskPaint;
|
||||||
|
private final Paint laserPaint;
|
||||||
|
private final Paint dotPaint;
|
||||||
|
private boolean isResult;
|
||||||
|
private final int maskColor, maskResultColor;
|
||||||
|
private final int laserColor;
|
||||||
|
private final int dotColor, dotResultColor;
|
||||||
|
private final Map<float[], Long> dots = new HashMap<float[], Long>(16);
|
||||||
|
private Rect frame;
|
||||||
|
private final Matrix matrix = new Matrix();
|
||||||
|
|
||||||
|
public ScannerView(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
final Resources res = getResources();
|
||||||
|
maskColor = res.getColor(R.color.scan_mask);
|
||||||
|
maskResultColor = res.getColor(R.color.scan_result_view);
|
||||||
|
laserColor = res.getColor(R.color.scan_laser);
|
||||||
|
dotColor = res.getColor(R.color.scan_dot);
|
||||||
|
dotResultColor = res.getColor(R.color.scan_result_dots);
|
||||||
|
|
||||||
|
maskPaint = new Paint();
|
||||||
|
maskPaint.setStyle(Style.FILL);
|
||||||
|
|
||||||
|
laserPaint = new Paint();
|
||||||
|
laserPaint.setStrokeWidth(res.getDimensionPixelSize(R.dimen.scan_laser_width));
|
||||||
|
laserPaint.setStyle(Style.STROKE);
|
||||||
|
|
||||||
|
dotPaint = new Paint();
|
||||||
|
dotPaint.setAlpha(DOT_OPACITY);
|
||||||
|
dotPaint.setStyle(Style.STROKE);
|
||||||
|
dotPaint.setStrokeWidth(res.getDimension(R.dimen.scan_dot_size));
|
||||||
|
dotPaint.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFraming(final Rect frame, final RectF framePreview, final int displayRotation,
|
||||||
|
final int cameraRotation, final boolean cameraFlip) {
|
||||||
|
this.frame = frame;
|
||||||
|
matrix.setRectToRect(framePreview, new RectF(frame), ScaleToFit.FILL);
|
||||||
|
matrix.postRotate(-displayRotation, frame.exactCenterX(), frame.exactCenterY());
|
||||||
|
matrix.postScale(cameraFlip ? -1 : 1, 1, frame.exactCenterX(), frame.exactCenterY());
|
||||||
|
matrix.postRotate(cameraRotation, frame.exactCenterX(), frame.exactCenterY());
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsResult(final boolean isResult) {
|
||||||
|
this.isResult = isResult;
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDot(final ResultPoint dot) {
|
||||||
|
dots.put(new float[] { dot.getX(), dot.getY() }, System.currentTimeMillis());
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(final Canvas canvas) {
|
||||||
|
if (frame == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
final int width = canvas.getWidth();
|
||||||
|
final int height = canvas.getHeight();
|
||||||
|
|
||||||
|
final float[] point = new float[2];
|
||||||
|
|
||||||
|
// draw mask darkened
|
||||||
|
maskPaint.setColor(isResult ? maskResultColor : maskColor);
|
||||||
|
canvas.drawRect(0, 0, width, frame.top, maskPaint);
|
||||||
|
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, maskPaint);
|
||||||
|
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, maskPaint);
|
||||||
|
canvas.drawRect(0, frame.bottom + 1, width, height, maskPaint);
|
||||||
|
|
||||||
|
if (isResult) {
|
||||||
|
laserPaint.setColor(dotResultColor);
|
||||||
|
laserPaint.setAlpha(160);
|
||||||
|
|
||||||
|
dotPaint.setColor(dotResultColor);
|
||||||
|
} else {
|
||||||
|
laserPaint.setColor(laserColor);
|
||||||
|
final boolean laserPhase = (now / 600) % 2 == 0;
|
||||||
|
laserPaint.setAlpha(laserPhase ? 160 : 255);
|
||||||
|
|
||||||
|
dotPaint.setColor(dotColor);
|
||||||
|
|
||||||
|
// schedule redraw
|
||||||
|
postInvalidateDelayed(LASER_ANIMATION_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawRect(frame, laserPaint);
|
||||||
|
|
||||||
|
// draw points
|
||||||
|
for (final Iterator<Map.Entry<float[], Long>> i = dots.entrySet().iterator(); i.hasNext();) {
|
||||||
|
final Map.Entry<float[], Long> entry = i.next();
|
||||||
|
final long age = now - entry.getValue();
|
||||||
|
if (age < DOT_TTL_MS) {
|
||||||
|
dotPaint.setAlpha((int) ((DOT_TTL_MS - age) * 256 / DOT_TTL_MS));
|
||||||
|
|
||||||
|
matrix.mapPoints(point, entry.getKey());
|
||||||
|
canvas.drawPoint(point[0], point[1], dotPaint);
|
||||||
|
} else {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,533 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2009 ZXing authors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.siacs.conversations.utils.zxing;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.ui.UriHandlerActivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
|
|
||||||
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
|
|
||||||
* project's source code.</p>
|
|
||||||
*
|
|
||||||
* <h2>Initiating a barcode scan</h2>
|
|
||||||
*
|
|
||||||
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
|
|
||||||
* for the result in your app.</p>
|
|
||||||
*
|
|
||||||
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
|
|
||||||
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
|
|
||||||
*
|
|
||||||
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
|
|
||||||
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
||||||
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
|
||||||
* if (scanResult != null) {
|
|
||||||
* // handle scan result
|
|
||||||
* }
|
|
||||||
* // else continue with any other code you need in the method
|
|
||||||
* ...
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>This is where you will handle a scan result.</p>
|
|
||||||
*
|
|
||||||
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
|
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
|
|
||||||
* integrator.initiateScan();
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
|
|
||||||
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
|
|
||||||
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
|
|
||||||
* method.</p>
|
|
||||||
*
|
|
||||||
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
|
|
||||||
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
|
|
||||||
* yes/no button labels can be changed.</p>
|
|
||||||
*
|
|
||||||
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
|
|
||||||
* to invoke the scanner. This can be used to set additional options not directly exposed by this
|
|
||||||
* simplified API.</p>
|
|
||||||
*
|
|
||||||
* <p>By default, this will only allow applications that are known to respond to this intent correctly
|
|
||||||
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
|
|
||||||
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
|
|
||||||
*
|
|
||||||
* <h2>Sharing text via barcode</h2>
|
|
||||||
*
|
|
||||||
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
|
|
||||||
*
|
|
||||||
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
|
|
||||||
*
|
|
||||||
* <h2>Enabling experimental barcode formats</h2>
|
|
||||||
*
|
|
||||||
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
|
|
||||||
* PDF417. Use {@link #initiateScan(Collection)} with
|
|
||||||
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
|
|
||||||
* formats.</p>
|
|
||||||
*
|
|
||||||
* @author Sean Owen
|
|
||||||
* @author Fred Lin
|
|
||||||
* @author Isaac Potoczny-Jones
|
|
||||||
* @author Brad Drehmer
|
|
||||||
* @author gcstang
|
|
||||||
*/
|
|
||||||
public class IntentIntegrator {
|
|
||||||
|
|
||||||
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
|
|
||||||
private static final String TAG = IntentIntegrator.class.getSimpleName();
|
|
||||||
|
|
||||||
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
|
|
||||||
public static final String DEFAULT_MESSAGE =
|
|
||||||
"This application requires Barcode Scanner. Would you like to install it?";
|
|
||||||
public static final String DEFAULT_YES = "Yes";
|
|
||||||
public static final String DEFAULT_NO = "No";
|
|
||||||
|
|
||||||
private static final String BS_PACKAGE = "com.google.zxing.client.android";
|
|
||||||
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
|
|
||||||
|
|
||||||
// supported barcode formats
|
|
||||||
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
|
|
||||||
public static final Collection<String> ONE_D_CODE_TYPES =
|
|
||||||
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
|
|
||||||
"ITF", "RSS_14", "RSS_EXPANDED");
|
|
||||||
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
|
|
||||||
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
|
|
||||||
|
|
||||||
public static final Collection<String> ALL_CODE_TYPES = null;
|
|
||||||
|
|
||||||
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
|
|
||||||
public static final List<String> TARGET_ALL_KNOWN = list(
|
|
||||||
BSPLUS_PACKAGE, // Barcode Scanner+
|
|
||||||
BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
|
|
||||||
BS_PACKAGE // Barcode Scanner
|
|
||||||
// What else supports this intent?
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should be FLAG_ACTIVITY_NEW_DOCUMENT in API 21+.
|
|
||||||
// Defined once here because the current value is deprecated, so generates just one warning
|
|
||||||
private static final int FLAG_NEW_DOC = Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
|
|
||||||
|
|
||||||
private final Activity activity;
|
|
||||||
private final Fragment fragment;
|
|
||||||
|
|
||||||
private String title;
|
|
||||||
private String message;
|
|
||||||
private String buttonYes;
|
|
||||||
private String buttonNo;
|
|
||||||
private List<String> targetApplications;
|
|
||||||
private final Map<String,Object> moreExtras = new HashMap<String,Object>(3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param activity {@link Activity} invoking the integration
|
|
||||||
*/
|
|
||||||
public IntentIntegrator(Activity activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.fragment = null;
|
|
||||||
initializeConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fragment {@link Fragment} invoking the integration.
|
|
||||||
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
|
|
||||||
* of an {@link Activity}
|
|
||||||
*/
|
|
||||||
public IntentIntegrator(Fragment fragment) {
|
|
||||||
this.activity = fragment.getActivity();
|
|
||||||
this.fragment = fragment;
|
|
||||||
initializeConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeConfiguration() {
|
|
||||||
title = DEFAULT_TITLE;
|
|
||||||
message = DEFAULT_MESSAGE;
|
|
||||||
buttonYes = DEFAULT_YES;
|
|
||||||
buttonNo = DEFAULT_NO;
|
|
||||||
targetApplications = TARGET_ALL_KNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitleByID(int titleID) {
|
|
||||||
title = activity.getString(titleID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessageByID(int messageID) {
|
|
||||||
message = activity.getString(messageID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getButtonYes() {
|
|
||||||
return buttonYes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setButtonYes(String buttonYes) {
|
|
||||||
this.buttonYes = buttonYes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setButtonYesByID(int buttonYesID) {
|
|
||||||
buttonYes = activity.getString(buttonYesID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getButtonNo() {
|
|
||||||
return buttonNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setButtonNo(String buttonNo) {
|
|
||||||
this.buttonNo = buttonNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setButtonNoByID(int buttonNoID) {
|
|
||||||
buttonNo = activity.getString(buttonNoID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<String> getTargetApplications() {
|
|
||||||
return targetApplications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setTargetApplications(List<String> targetApplications) {
|
|
||||||
if (targetApplications.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("No target applications");
|
|
||||||
}
|
|
||||||
this.targetApplications = targetApplications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSingleTargetApplication(String targetApplication) {
|
|
||||||
this.targetApplications = Collections.singletonList(targetApplication);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String,?> getMoreExtras() {
|
|
||||||
return moreExtras;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void addExtra(String key, Object value) {
|
|
||||||
moreExtras.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates a scan for all known barcode types with the default camera.
|
|
||||||
*
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise.
|
|
||||||
*/
|
|
||||||
public final AlertDialog initiateScan() {
|
|
||||||
return initiateScan(ALL_CODE_TYPES, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates a scan for all known barcode types with the specified camera.
|
|
||||||
*
|
|
||||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise.
|
|
||||||
*/
|
|
||||||
public final AlertDialog initiateScan(int cameraId) {
|
|
||||||
return initiateScan(ALL_CODE_TYPES, cameraId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
|
|
||||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
|
||||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
|
||||||
*
|
|
||||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise.
|
|
||||||
*/
|
|
||||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
|
|
||||||
return initiateScan(desiredBarcodeFormats, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
|
|
||||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
|
||||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
|
||||||
*
|
|
||||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
|
||||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise
|
|
||||||
*/
|
|
||||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
|
|
||||||
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
|
|
||||||
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
|
|
||||||
// check which types of codes to scan for
|
|
||||||
if (desiredBarcodeFormats != null) {
|
|
||||||
// set the desired barcode types
|
|
||||||
StringBuilder joinedByComma = new StringBuilder();
|
|
||||||
for (String format : desiredBarcodeFormats) {
|
|
||||||
if (joinedByComma.length() > 0) {
|
|
||||||
joinedByComma.append(',');
|
|
||||||
}
|
|
||||||
joinedByComma.append(format);
|
|
||||||
}
|
|
||||||
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check requested camera ID
|
|
||||||
if (cameraId >= 0) {
|
|
||||||
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
|
|
||||||
}
|
|
||||||
|
|
||||||
String targetAppPackage = findTargetAppPackage(intentScan);
|
|
||||||
if (targetAppPackage == null) {
|
|
||||||
return showDownloadDialog();
|
|
||||||
}
|
|
||||||
intentScan.setPackage(targetAppPackage);
|
|
||||||
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
intentScan.addFlags(FLAG_NEW_DOC);
|
|
||||||
attachMoreExtras(intentScan);
|
|
||||||
startActivityForResult(intentScan, REQUEST_CODE);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start an activity. This method is defined to allow different methods of activity starting for
|
|
||||||
* newer versions of Android and for compatibility library.
|
|
||||||
*
|
|
||||||
* @param intent Intent to start.
|
|
||||||
* @param code Request code for the activity
|
|
||||||
* @see Activity#startActivityForResult(Intent, int)
|
|
||||||
* @see Fragment#startActivityForResult(Intent, int)
|
|
||||||
*/
|
|
||||||
protected void startActivityForResult(Intent intent, int code) {
|
|
||||||
if (fragment == null) {
|
|
||||||
activity.startActivityForResult(intent, code);
|
|
||||||
} else {
|
|
||||||
fragment.startActivityForResult(intent, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String findTargetAppPackage(Intent intent) {
|
|
||||||
PackageManager pm = activity.getPackageManager();
|
|
||||||
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
|
||||||
if (availableApps != null) {
|
|
||||||
for (String targetApp : targetApplications) {
|
|
||||||
if (contains(availableApps, targetApp)) {
|
|
||||||
return targetApp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
|
|
||||||
for (ResolveInfo availableApp : availableApps) {
|
|
||||||
String packageName = availableApp.activityInfo.packageName;
|
|
||||||
if (targetApp.equals(packageName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AlertDialog showDownloadDialog() {
|
|
||||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
|
||||||
downloadDialog.setTitle(title);
|
|
||||||
downloadDialog.setMessage(message);
|
|
||||||
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
String packageName;
|
|
||||||
if (targetApplications.contains(BS_PACKAGE)) {
|
|
||||||
// Prefer to suggest download of BS if it's anywhere in the list
|
|
||||||
packageName = BS_PACKAGE;
|
|
||||||
} else {
|
|
||||||
// Otherwise, first option:
|
|
||||||
packageName = targetApplications.get(0);
|
|
||||||
}
|
|
||||||
Uri uri = Uri.parse("market://details?id=" + packageName);
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
|
||||||
try {
|
|
||||||
if (fragment == null) {
|
|
||||||
activity.startActivity(intent);
|
|
||||||
finishIfNeeded();
|
|
||||||
} else {
|
|
||||||
fragment.startActivity(intent);
|
|
||||||
}
|
|
||||||
} catch (ActivityNotFoundException anfe) {
|
|
||||||
// Hmm, market is not installed
|
|
||||||
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
downloadDialog.setNegativeButton(buttonNo, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
finishIfNeeded();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
downloadDialog.setCancelable(true);
|
|
||||||
downloadDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
|
||||||
@Override
|
|
||||||
public void onCancel(DialogInterface dialogInterface) {
|
|
||||||
finishIfNeeded();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return downloadDialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishIfNeeded() {
|
|
||||||
if (fragment != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (activity != null && activity instanceof UriHandlerActivity) {
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Call this from your {@link Activity}'s
|
|
||||||
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
|
|
||||||
*
|
|
||||||
* @param requestCode request code from {@code onActivityResult()}
|
|
||||||
* @param resultCode result code from {@code onActivityResult()}
|
|
||||||
* @param intent {@link Intent} from {@code onActivityResult()}
|
|
||||||
* @return null if the event handled here was not related to this class, or
|
|
||||||
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
|
|
||||||
* the fields will be null.
|
|
||||||
*/
|
|
||||||
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
||||||
if (requestCode == REQUEST_CODE) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
String contents = intent.getStringExtra("SCAN_RESULT");
|
|
||||||
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
|
|
||||||
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
|
|
||||||
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
|
|
||||||
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
|
|
||||||
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
|
|
||||||
return new IntentResult(contents,
|
|
||||||
formatName,
|
|
||||||
rawBytes,
|
|
||||||
orientation,
|
|
||||||
errorCorrectionLevel);
|
|
||||||
}
|
|
||||||
return new IntentResult();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defaults to type "TEXT_TYPE".
|
|
||||||
*
|
|
||||||
* @param text the text string to encode as a barcode
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise
|
|
||||||
* @see #shareText(CharSequence, CharSequence)
|
|
||||||
*/
|
|
||||||
public final AlertDialog shareText(CharSequence text) {
|
|
||||||
return shareText(text, "TEXT_TYPE");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shares the given text by encoding it as a barcode, such that another user can
|
|
||||||
* scan the text off the screen of the device.
|
|
||||||
*
|
|
||||||
* @param text the text string to encode as a barcode
|
|
||||||
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
|
|
||||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
|
||||||
* if a prompt was needed, or null otherwise
|
|
||||||
*/
|
|
||||||
public final AlertDialog shareText(CharSequence text, CharSequence type) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
intent.setAction(BS_PACKAGE + ".ENCODE");
|
|
||||||
intent.putExtra("ENCODE_TYPE", type);
|
|
||||||
intent.putExtra("ENCODE_DATA", text);
|
|
||||||
String targetAppPackage = findTargetAppPackage(intent);
|
|
||||||
if (targetAppPackage == null) {
|
|
||||||
return showDownloadDialog();
|
|
||||||
}
|
|
||||||
intent.setPackage(targetAppPackage);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
intent.addFlags(FLAG_NEW_DOC);
|
|
||||||
attachMoreExtras(intent);
|
|
||||||
if (fragment == null) {
|
|
||||||
activity.startActivity(intent);
|
|
||||||
} else {
|
|
||||||
fragment.startActivity(intent);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> list(String... values) {
|
|
||||||
return Collections.unmodifiableList(Arrays.asList(values));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attachMoreExtras(Intent intent) {
|
|
||||||
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
|
|
||||||
String key = entry.getKey();
|
|
||||||
Object value = entry.getValue();
|
|
||||||
// Kind of hacky
|
|
||||||
if (value instanceof Integer) {
|
|
||||||
intent.putExtra(key, (Integer) value);
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
intent.putExtra(key, (Long) value);
|
|
||||||
} else if (value instanceof Boolean) {
|
|
||||||
intent.putExtra(key, (Boolean) value);
|
|
||||||
} else if (value instanceof Double) {
|
|
||||||
intent.putExtra(key, (Double) value);
|
|
||||||
} else if (value instanceof Float) {
|
|
||||||
intent.putExtra(key, (Float) value);
|
|
||||||
} else if (value instanceof Bundle) {
|
|
||||||
intent.putExtra(key, (Bundle) value);
|
|
||||||
} else {
|
|
||||||
intent.putExtra(key, value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2009 ZXing authors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.siacs.conversations.utils.zxing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
|
|
||||||
*
|
|
||||||
* @author Sean Owen
|
|
||||||
*/
|
|
||||||
public final class IntentResult {
|
|
||||||
|
|
||||||
private final String contents;
|
|
||||||
private final String formatName;
|
|
||||||
private final byte[] rawBytes;
|
|
||||||
private final Integer orientation;
|
|
||||||
private final String errorCorrectionLevel;
|
|
||||||
|
|
||||||
IntentResult() {
|
|
||||||
this(null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
IntentResult(String contents,
|
|
||||||
String formatName,
|
|
||||||
byte[] rawBytes,
|
|
||||||
Integer orientation,
|
|
||||||
String errorCorrectionLevel) {
|
|
||||||
this.contents = contents;
|
|
||||||
this.formatName = formatName;
|
|
||||||
this.rawBytes = rawBytes;
|
|
||||||
this.orientation = orientation;
|
|
||||||
this.errorCorrectionLevel = errorCorrectionLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return raw content of barcode
|
|
||||||
*/
|
|
||||||
public String getContents() {
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
|
|
||||||
*/
|
|
||||||
public String getFormatName() {
|
|
||||||
return formatName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return raw bytes of the barcode content, if applicable, or null otherwise
|
|
||||||
*/
|
|
||||||
public byte[] getRawBytes() {
|
|
||||||
return rawBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
|
|
||||||
*/
|
|
||||||
public Integer getOrientation() {
|
|
||||||
return orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return name of the error correction level used in the barcode, if applicable
|
|
||||||
*/
|
|
||||||
public String getErrorCorrectionLevel() {
|
|
||||||
return errorCorrectionLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
|
|
||||||
return "Format: " + formatName + '\n' +
|
|
||||||
"Contents: " + contents + '\n' +
|
|
||||||
"Raw bytes: (" + rawBytesLength + " bytes)\n" +
|
|
||||||
"Orientation: " + orientation + '\n' +
|
|
||||||
"EC level: " + errorCorrectionLevel + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
17
src/main/res/layout/activity_scan.xml
Normal file
17
src/main/res/layout/activity_scan.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
|
||||||
|
<TextureView
|
||||||
|
android:id="@+id/scan_activity_preview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:keepScreenOn="true" />
|
||||||
|
|
||||||
|
<eu.siacs.conversations.ui.widget.ScannerView
|
||||||
|
android:id="@+id/scan_activity_mask"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</merge>
|
|
@ -23,4 +23,11 @@
|
||||||
<color name="bubble">#ff4b9b4a</color>
|
<color name="bubble">#ff4b9b4a</color>
|
||||||
<color name="unreadcountlight">#ff4b9b4a</color>
|
<color name="unreadcountlight">#ff4b9b4a</color>
|
||||||
<color name="unreadcountdark">#ff326130</color>
|
<color name="unreadcountdark">#ff326130</color>
|
||||||
|
|
||||||
|
<!-- scanner -->
|
||||||
|
<color name="scan_mask">#60000000</color>
|
||||||
|
<color name="scan_laser">#cc0000</color>
|
||||||
|
<color name="scan_dot">#ff6600</color>
|
||||||
|
<color name="scan_result_view">#b0000000</color>
|
||||||
|
<color name="scan_result_dots">#c099cc00</color>
|
||||||
</resources>
|
</resources>
|
|
@ -11,4 +11,8 @@
|
||||||
<dimen name="audio_player_width">224dp</dimen>
|
<dimen name="audio_player_width">224dp</dimen>
|
||||||
<dimen name="swipe_handle_size">32dp</dimen>
|
<dimen name="swipe_handle_size">32dp</dimen>
|
||||||
<dimen name="avatar_item_distance">16dp</dimen>
|
<dimen name="avatar_item_distance">16dp</dimen>
|
||||||
|
|
||||||
|
<!-- scanner -->
|
||||||
|
<dimen name="scan_laser_width">4dp</dimen>
|
||||||
|
<dimen name="scan_dot_size">8dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -743,4 +743,5 @@
|
||||||
<string name="mtm_cert_details">Certificate details:</string>
|
<string name="mtm_cert_details">Certificate details:</string>
|
||||||
<string name="mtm_notification">Certificate Verification</string>
|
<string name="mtm_notification">Certificate Verification</string>
|
||||||
<string name="once">Once</string>
|
<string name="once">Once</string>
|
||||||
|
<string name="qr_code_scanner_needs_access_to_camera">The QR code scanner needs access to the camera</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -177,4 +177,12 @@
|
||||||
<item name="TextSizeHeadline">22sp</item>
|
<item name="TextSizeHeadline">22sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ConversationsTheme.FullScreen" parent="@style/Theme.AppCompat.Light">
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowActionBar">false</item>
|
||||||
|
<item name="android:windowFullscreen">true</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue