diff options
Diffstat (limited to 'src/main/java/de/pixart/messenger/ui')
9 files changed, 849 insertions, 38 deletions
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index b80469649..2783a4b1f 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -312,6 +312,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) { @@ -323,8 +324,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation } @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 { @@ -368,7 +373,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)); @@ -451,6 +461,12 @@ public class ConversationActivity extends XmppActivity implements OnConversation } @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putParcelable("intent", getIntent()); + super.onSaveInstanceState(savedInstanceState); + } + + @Override protected void onStart() { final int theme = findTheme(); if (this.mTheme != theme) { diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java index 35e1e02b7..476cc267d 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java @@ -144,7 +144,10 @@ public class ConversationsOverviewFragment extends XmppFragment { @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() { diff --git a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java index a549c76e3..3406d0c46 100644 --- a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java +++ b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java @@ -15,7 +15,6 @@ import android.widget.TextView; import android.widget.Toast; import java.security.cert.X509Certificate; -import java.util.Arrays; import de.pixart.messenger.Config; import de.pixart.messenger.R; @@ -25,8 +24,6 @@ import de.pixart.messenger.databinding.ContactKeyBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.XmppUri; -import de.pixart.messenger.utils.zxing.IntentIntegrator; -import de.pixart.messenger.utils.zxing.IntentResult; public abstract class OmemoActivity extends XmppActivity { @@ -74,7 +71,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; @@ -82,7 +79,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); @@ -91,7 +88,7 @@ public abstract class OmemoActivity extends XmppActivity { } else { this.mPendingFingerprintVerificationUri =uri; } - } + }*/ } protected abstract void processFingerprintVerification(XmppUri uri); diff --git a/src/main/java/de/pixart/messenger/ui/ScanActivity.java b/src/main/java/de/pixart/messenger/ui/ScanActivity.java new file mode 100644 index 000000000..d44f50512 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/ScanActivity.java @@ -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 de.pixart.messenger.ui; + +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 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 java.util.EnumMap; +import java.util.Map; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.ui.service.CameraManager; +import de.pixart.messenger.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); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index 385d0a68f..3499b8f00 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -16,7 +16,6 @@ import android.widget.Toast; import org.whispersystems.libsignal.IdentityKey; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,7 +32,6 @@ import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.XmppUri; -import de.pixart.messenger.utils.zxing.IntentIntegrator; import de.pixart.messenger.xmpp.OnKeyStatusUpdated; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; @@ -130,7 +128,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; } } diff --git a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java index c4374df33..d71feb3f7 100644 --- a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java +++ b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java @@ -1,21 +1,60 @@ package de.pixart.messenger.ui; +import android.Manifest; import android.app.Activity; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; -import java.util.Arrays; import java.util.List; +import de.pixart.messenger.R; import de.pixart.messenger.persistance.DatabaseBackend; import de.pixart.messenger.utils.XmppUri; -import de.pixart.messenger.utils.zxing.IntentIntegrator; -import de.pixart.messenger.utils.zxing.IntentResult; import de.pixart.messenger.xmpp.jid.Jid; public class UriHandlerActivity extends AppCompatActivity { + 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 public void onStart() { @@ -24,6 +63,12 @@ public class UriHandlerActivity extends AppCompatActivity { } @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putBoolean("handled", this.handled); + super.onSaveInstanceState(savedInstanceState); + } + + @Override public void onNewIntent(Intent intent) { handleIntent(intent); } @@ -31,7 +76,7 @@ public class UriHandlerActivity extends AppCompatActivity { private void handleUri(Uri uri) { final Intent intent; 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) { intent = new Intent(getApplicationContext(), WelcomeActivity.class); @@ -70,18 +115,24 @@ public class UriHandlerActivity extends AppCompatActivity { } 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: - new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC", "QR_CODE")); + Intent intent = new Intent(this, ScanActivity.class); + startActivityForResult(intent, REQUEST_SCAN_QR_CODE); return; } @@ -90,23 +141,14 @@ public class UriHandlerActivity extends AppCompatActivity { @Override 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)); + 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(); - super.onActivityResult(requestCode, requestCode, 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); } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java index ea9bcc5a8..ded1f90c7 100644 --- a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java +++ b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java @@ -1,10 +1,10 @@ package de.pixart.messenger.ui; -import android.support.v7.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.view.Menu; import android.view.View; import android.widget.Button; @@ -23,8 +23,6 @@ import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.XmppUri; -import de.pixart.messenger.utils.zxing.IntentIntegrator; -import de.pixart.messenger.utils.zxing.IntentResult; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; @@ -208,7 +206,8 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION); if (this.mode == MODE_SCAN_FINGERPRINT) { - new IntentIntegrator(this).initiateScan(); + // todo + // new IntentIntegrator(this).initiateScan(); return false; } return true; @@ -219,7 +218,8 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { - if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { + // todo + /*if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) { IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); if (scanResult != null && scanResult.getFormatName() != null) { String data = scanResult.getContents(); @@ -233,7 +233,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer } else { finish(); } - } + }*/ super.onActivityResult(requestCode, requestCode, intent); } diff --git a/src/main/java/de/pixart/messenger/ui/service/CameraManager.java b/src/main/java/de/pixart/messenger/ui/service/CameraManager.java new file mode 100644 index 000000000..1c232928f --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/service/CameraManager.java @@ -0,0 +1,305 @@ +/* + * 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 de.pixart.messenger.ui.service; + +import android.annotation.SuppressLint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.util.Log; +import android.view.TextureView; + +import com.google.zxing.PlanarYUVLuminanceSource; + +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 de.pixart.messenger.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 android.hardware.Camera.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); + } + + 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; + } + + public void setTorch(final boolean enabled) { + if (enabled != getTorchEnabled(camera)) + setTorchEnabled(camera, enabled); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/widget/ScannerView.java b/src/main/java/de/pixart/messenger/ui/widget/ScannerView.java new file mode 100644 index 000000000..0a2a80039 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/ScannerView.java @@ -0,0 +1,158 @@ +/* + * 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 de.pixart.messenger.ui.widget; + +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 com.google.zxing.ResultPoint; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import de.pixart.messenger.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 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 final Matrix matrix = new Matrix(); + private boolean isResult; + private Rect frame; + + 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(); + } + } + } +}
\ No newline at end of file |