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.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.CAMERA" /> | ||||
| 
 | ||||
|     <uses-permission | ||||
|         android:name="android.permission.READ_PHONE_STATE" | ||||
|         tools:node="remove" /> | ||||
|  | @ -56,10 +58,14 @@ | |||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".ui.ScanActivity" | ||||
|             android:screenOrientation="portrait" | ||||
|             android:theme="@style/ConversationsTheme.FullScreen" | ||||
|             android:windowSoftInputMode="stateAlwaysHidden" /> | ||||
|         <activity | ||||
|             android:name=".ui.UriHandlerActivity" | ||||
|             android:label="@string/title_activity_start_conversation" | ||||
|             android:launchMode="singleTop"> | ||||
|             android:label="@string/title_activity_start_conversation"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -252,6 +252,7 @@ public class ConversationActivity extends XmppActivity implements OnConversation | |||
| 	} | ||||
| 
 | ||||
| 	private boolean processViewIntent(Intent intent) { | ||||
| 		Log.d(Config.LOGTAG,"process view intent"); | ||||
| 		String uuid = intent.getStringExtra(EXTRA_CONVERSATION); | ||||
| 		Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null; | ||||
| 		if (conversation == null) { | ||||
|  | @ -262,9 +263,13 @@ public class ConversationActivity extends XmppActivity implements OnConversation | |||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { | ||||
| 		UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onActivityResult(int requestCode, int resultCode, final Intent data) { | ||||
| 		Log.d(Config.LOGTAG,"on activity result"); | ||||
| 		if (resultCode == RESULT_OK) { | ||||
| 			handlePositiveActivityResult(requestCode, data); | ||||
| 		} else { | ||||
|  | @ -308,7 +313,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation | |||
| 		this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview); | ||||
| 		this.initializeFragments(); | ||||
| 		this.invalidateActionBarTitle(); | ||||
| 		final Intent intent = getIntent(); | ||||
| 		final Intent intent; | ||||
| 		if (savedInstanceState == null) { | ||||
| 			intent = getIntent(); | ||||
| 		} else { | ||||
| 			intent = savedInstanceState.getParcelable("intent"); | ||||
| 		} | ||||
| 		if (isViewIntent(intent)) { | ||||
| 			pendingViewIntent.push(intent); | ||||
| 			setIntent(createLauncherIntent(this)); | ||||
|  | @ -377,6 +387,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation | |||
| 		return super.onOptionsItemSelected(item); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onSaveInstanceState(Bundle savedInstanceState) { | ||||
| 		savedInstanceState.putParcelable("intent", getIntent()); | ||||
| 		super.onSaveInstanceState(savedInstanceState); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void onStart() { | ||||
| 		final int theme = findTheme(); | ||||
|  |  | |||
|  | @ -152,10 +152,16 @@ public class ConversationsOverviewFragment extends XmppFragment implements Enhan | |||
| 	@Override | ||||
| 	public void onSaveInstanceState(Bundle 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() { | ||||
| 		if (this.binding == null) { | ||||
| 			return null; | ||||
| 		} | ||||
| 		int position = this.binding.list.getFirstVisiblePosition(); | ||||
| 		final View view = this.binding.list.getChildAt(0); | ||||
| 		if (view != null) { | ||||
|  |  | |||
|  | @ -25,9 +25,6 @@ import eu.siacs.conversations.databinding.ContactKeyBinding; | |||
| import eu.siacs.conversations.entities.Account; | ||||
| import eu.siacs.conversations.utils.CryptoHelper; | ||||
| 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 { | ||||
| 
 | ||||
|  | @ -76,7 +73,7 @@ public abstract class OmemoActivity extends XmppActivity { | |||
|                 copyOmemoFingerprint(mSelectedFingerprint); | ||||
|                 break; | ||||
|             case R.id.verify_scan: | ||||
|                 new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); | ||||
|                 //new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); | ||||
|                 break; | ||||
|         } | ||||
|         return true; | ||||
|  | @ -84,7 +81,7 @@ public abstract class OmemoActivity extends XmppActivity { | |||
| 
 | ||||
|     @Override | ||||
|     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) { | ||||
|             String data = scanResult.getContents(); | ||||
|             XmppUri uri = new XmppUri(data); | ||||
|  | @ -93,7 +90,7 @@ public abstract class OmemoActivity extends XmppActivity { | |||
|             } else { | ||||
|                 this.mPendingFingerprintVerificationUri =uri; | ||||
|             } | ||||
|         } | ||||
|         }*/ | ||||
|     } | ||||
| 
 | ||||
|     protected abstract void processFingerprintVerification(XmppUri uri); | ||||
|  |  | |||
|  | @ -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.utils.CryptoHelper; | ||||
| import eu.siacs.conversations.utils.XmppUri; | ||||
| import eu.siacs.conversations.utils.zxing.IntentIntegrator; | ||||
| import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; | ||||
| import eu.siacs.conversations.xmpp.jid.InvalidJidException; | ||||
| import eu.siacs.conversations.xmpp.jid.Jid; | ||||
| 
 | ||||
| import static android.databinding.DataBindingUtil.inflate; | ||||
| 
 | ||||
| public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { | ||||
| 	private List<Jid> contactJids; | ||||
|  | @ -135,7 +133,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat | |||
| 				if (hasPendingKeyFetches()) { | ||||
| 					Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show(); | ||||
| 				} else { | ||||
| 					new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); | ||||
| 					//new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); | ||||
| 					return true; | ||||
| 				} | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,115 +1,154 @@ | |||
| package eu.siacs.conversations.ui; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| 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.net.Uri; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import eu.siacs.conversations.persistance.DatabaseBackend; | ||||
| 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; | ||||
| 
 | ||||
| public class UriHandlerActivity extends AppCompatActivity { | ||||
|     public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStart() { | ||||
|         super.onStart(); | ||||
|         handleIntent(getIntent()); | ||||
|     } | ||||
| 	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; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNewIntent(Intent intent) { | ||||
|         handleIntent(intent); | ||||
|     } | ||||
| 	private boolean handled = false; | ||||
| 
 | ||||
|     private void handleUri(Uri uri) { | ||||
|         final Intent intent; | ||||
|         final XmppUri xmppUri = new XmppUri(uri); | ||||
|         final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(); | ||||
| 	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); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|         if (accounts.size() == 0) { | ||||
|             intent = new Intent(getApplicationContext(), WelcomeActivity.class); | ||||
|             WelcomeActivity.addInviteUri(intent, xmppUri); | ||||
|             startActivity(intent); | ||||
|             return; | ||||
|         } | ||||
| 	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(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|         if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) { | ||||
|             final Jid jid = xmppUri.getJid(); | ||||
|             final String body = xmppUri.getBody(); | ||||
| 	@Override | ||||
| 	protected void onCreate(Bundle savedInstanceState) { | ||||
| 		super.onCreate(savedInstanceState); | ||||
| 		this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled",false); | ||||
| 	} | ||||
| 
 | ||||
|             if (jid != null) { | ||||
|                 intent = new Intent(getApplicationContext(), ShareViaAccountActivity.class); | ||||
|                 intent.putExtra(ShareViaAccountActivity.EXTRA_CONTACT, jid.toString()); | ||||
|                 intent.putExtra(ShareViaAccountActivity.EXTRA_BODY, body); | ||||
|             } else { | ||||
|                 intent = new Intent(getApplicationContext(), ShareWithActivity.class); | ||||
|                 intent.setAction(Intent.ACTION_SEND); | ||||
|                 intent.setType("text/plain"); | ||||
|                 intent.putExtra(Intent.EXTRA_TEXT, body); | ||||
|             } | ||||
|         } else if (accounts.contains(xmppUri.getJid())) { | ||||
|             intent = new Intent(getApplicationContext(), EditAccountActivity.class); | ||||
|             intent.setAction(Intent.ACTION_VIEW); | ||||
|             intent.putExtra("jid", xmppUri.getJid().toBareJid().toString()); | ||||
|             intent.setData(uri); | ||||
|         } else { | ||||
|             intent = new Intent(getApplicationContext(), StartConversationActivity.class); | ||||
|             intent.setAction(Intent.ACTION_VIEW); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); | ||||
|             intent.setData(uri); | ||||
|         } | ||||
| 	@Override | ||||
| 	public void onStart() { | ||||
| 		super.onStart(); | ||||
| 		handleIntent(getIntent()); | ||||
| 	} | ||||
| 
 | ||||
|         startActivity(intent); | ||||
|     } | ||||
| 	@Override | ||||
| 	public void onSaveInstanceState(Bundle savedInstanceState) { | ||||
| 		savedInstanceState.putBoolean("handled", this.handled); | ||||
| 		super.onSaveInstanceState(savedInstanceState); | ||||
| 	} | ||||
| 
 | ||||
|     private void handleIntent(Intent data) { | ||||
|         if (data == null || data.getAction() == null) { | ||||
|             finish(); | ||||
|             return; | ||||
|         } | ||||
| 	@Override | ||||
| 	public void onNewIntent(Intent intent) { | ||||
| 		handleIntent(intent); | ||||
| 	} | ||||
| 
 | ||||
|         switch (data.getAction()) { | ||||
|             case Intent.ACTION_VIEW: | ||||
|             case Intent.ACTION_SENDTO: | ||||
|                 handleUri(data.getData()); | ||||
|                 break; | ||||
|             case ACTION_SCAN_QR_CODE: | ||||
|                 new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC", "QR_CODE")); | ||||
|                 return; | ||||
|         } | ||||
| 	private void handleUri(Uri uri) { | ||||
| 		final Intent intent; | ||||
| 		final XmppUri xmppUri = new XmppUri(uri); | ||||
| 		final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(); //TODO only look at enabled accounts | ||||
| 
 | ||||
|         finish(); | ||||
|     } | ||||
| 		if (accounts.size() == 0) { | ||||
| 			intent = new Intent(getApplicationContext(), WelcomeActivity.class); | ||||
| 			WelcomeActivity.addInviteUri(intent, xmppUri); | ||||
| 			startActivity(intent); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|     @Override | ||||
|     public void onActivityResult(int requestCode, int resultCode, Intent intent) { | ||||
|         if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { | ||||
|             IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); | ||||
| 		if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) { | ||||
| 			final Jid jid = xmppUri.getJid(); | ||||
| 			final String body = xmppUri.getBody(); | ||||
| 
 | ||||
|             if (scanResult != null && scanResult.getFormatName() != null) { | ||||
|                 String data = scanResult.getContents(); | ||||
|                 handleUri(Uri.parse(data)); | ||||
|             } | ||||
|         } | ||||
| 			if (jid != null) { | ||||
| 				intent = new Intent(getApplicationContext(), ShareViaAccountActivity.class); | ||||
| 				intent.putExtra(ShareViaAccountActivity.EXTRA_CONTACT, jid.toString()); | ||||
| 				intent.putExtra(ShareViaAccountActivity.EXTRA_BODY, body); | ||||
| 			} else { | ||||
| 				intent = new Intent(getApplicationContext(), ShareWithActivity.class); | ||||
| 				intent.setAction(Intent.ACTION_SEND); | ||||
| 				intent.setType("text/plain"); | ||||
| 				intent.putExtra(Intent.EXTRA_TEXT, body); | ||||
| 			} | ||||
| 		} else if (accounts.contains(xmppUri.getJid())) { | ||||
| 			intent = new Intent(getApplicationContext(), EditAccountActivity.class); | ||||
| 			intent.setAction(Intent.ACTION_VIEW); | ||||
| 			intent.putExtra("jid", xmppUri.getJid().toBareJid().toString()); | ||||
| 			intent.setData(uri); | ||||
| 		} else { | ||||
| 			intent = new Intent(getApplicationContext(), StartConversationActivity.class); | ||||
| 			intent.setAction(Intent.ACTION_VIEW); | ||||
| 			intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); | ||||
| 			intent.setData(uri); | ||||
| 		} | ||||
| 
 | ||||
|         finish(); | ||||
|         super.onActivityResult(requestCode, requestCode, intent); | ||||
|     } | ||||
| 		startActivity(intent); | ||||
| 	} | ||||
| 
 | ||||
|     public static void scan(Activity activity) { | ||||
|         Intent intent = new Intent(activity, UriHandlerActivity.class); | ||||
|         intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); | ||||
|         activity.startActivity(intent); | ||||
|     } | ||||
| 	private void handleIntent(Intent data) { | ||||
| 		if (handled) { | ||||
| 			return; | ||||
| 		} | ||||
| 		if (data == null || data.getAction() == null) { | ||||
| 			finish(); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		handled = true; | ||||
| 
 | ||||
| 		switch (data.getAction()) { | ||||
| 			case Intent.ACTION_VIEW: | ||||
| 			case Intent.ACTION_SENDTO: | ||||
| 				handleUri(data.getData()); | ||||
| 				break; | ||||
| 			case ACTION_SCAN_QR_CODE: | ||||
| 				Intent intent = new Intent(this, ScanActivity.class); | ||||
| 				startActivityForResult(intent, REQUEST_SCAN_QR_CODE); | ||||
| 				return; | ||||
| 		} | ||||
| 
 | ||||
| 		finish(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void onActivityResult(int requestCode, int resultCode, Intent 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); | ||||
| 			} | ||||
| 		} | ||||
| 		finish(); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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'; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -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="unreadcountlight">#ff4b9b4a</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> | ||||
|  | @ -11,4 +11,8 @@ | |||
| 	<dimen name="audio_player_width">224dp</dimen> | ||||
| 	<dimen name="swipe_handle_size">32dp</dimen> | ||||
| 	<dimen name="avatar_item_distance">16dp</dimen> | ||||
| 
 | ||||
| 	<!-- scanner --> | ||||
| 	<dimen name="scan_laser_width">4dp</dimen> | ||||
| 	<dimen name="scan_dot_size">8dp</dimen> | ||||
| </resources> | ||||
|  |  | |||
|  | @ -743,4 +743,5 @@ | |||
| 	<string name="mtm_cert_details">Certificate details:</string> | ||||
| 	<string name="mtm_notification">Certificate Verification</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> | ||||
|  |  | |||
|  | @ -177,4 +177,12 @@ | |||
|         <item name="TextSizeHeadline">22sp</item> | ||||
|     </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> | ||||
		Loading…
	
		Reference in New Issue
	
	 Daniel Gultsch
						Daniel Gultsch