integrate qr code scanner. temporarily break omemo activity scan

This commit is contained in:
Daniel Gultsch 2018-02-25 23:58:56 +01:00
parent 6652135746
commit dfb4e4eb46
16 changed files with 954 additions and 726 deletions

View file

@ -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" />

View file

@ -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();

View file

@ -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) {

View file

@ -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);

View 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);
}
}
}
}

View file

@ -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;
} }
} }

View file

@ -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);
} }
} }

View file

@ -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;
}
}

View 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();
}
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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';
}
}

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>