From 06deba4bd4d7702e12fec98c7d7ffa12efeffd89 Mon Sep 17 00:00:00 2001 From: goapunk Date: Thu, 31 May 2018 14:25:27 +0200 Subject: [PATCH] Create a dedicated qrCodeView --- .../android/activity/ActivityComponent.java | 4 +- .../android/keyagreement/CameraView.java | 28 ++- .../keyagreement/KeyAgreementActivity.java | 4 +- ...ragment.java => KeyAgreementFragment.java} | 181 +++++++----------- .../briar/android/view/QrCodeView.java | 61 ++++++ .../layout-land/fragment_keyagreement_qr.xml | 110 ----------- .../res/layout/fragment_keyagreement_qr.xml | 41 +--- .../src/main/res/layout/qr_code_view.xml | 43 +++++ 8 files changed, 202 insertions(+), 270 deletions(-) rename briar-android/src/main/java/org/briarproject/briar/android/keyagreement/{ShowQrCodeFragment.java => KeyAgreementFragment.java} (71%) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/view/QrCodeView.java delete mode 100644 briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml create mode 100644 briar-android/src/main/res/layout/qr_code_view.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 50b541687..1d92a31f3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -29,7 +29,7 @@ import org.briarproject.briar.android.introduction.IntroductionMessageFragment; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.IntroFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; -import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment; +import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.DozeFragment; @@ -188,7 +188,7 @@ public interface ActivityComponent { void inject(IntroFragment fragment); - void inject(ShowQrCodeFragment fragment); + void inject(KeyAgreementFragment fragment); void inject(ContactChooserFragment fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java index aa6ba31ac..d9cf8d01f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraView.java @@ -9,10 +9,12 @@ import android.hardware.Camera.Size; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.util.AttributeSet; +import android.view.Display; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import android.view.WindowManager; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; @@ -21,6 +23,7 @@ import java.io.IOException; import java.util.List; import java.util.logging.Logger; +import static android.content.Context.WINDOW_SERVICE; import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK; import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT; import static android.hardware.Camera.Parameters.FLASH_MODE_OFF; @@ -97,7 +100,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback, } @UiThread - public void start(int rotationDegrees) throws CameraException { + public void start() throws CameraException { LOG.info("Opening camera"); try { int cameras = Camera.getNumberOfCameras(); @@ -122,7 +125,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback, } catch (RuntimeException e) { throw new CameraException(e); } - setDisplayOrientation(rotationDegrees); + setDisplayOrientation(getScreenRotationDegrees()); // Use barcode scene mode if it's available Parameters params = camera.getParameters(); params = setSceneMode(camera, params); @@ -157,6 +160,27 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback, camera = null; } + /** + * See {@link Camera#setDisplayOrientation(int)}. + */ + private int getScreenRotationDegrees() { + WindowManager wm = + (WindowManager) getContext().getSystemService(WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + switch (d.getRotation()) { + case Surface.ROTATION_0: + return 0; + case Surface.ROTATION_90: + return 90; + case Surface.ROTATION_180: + return 180; + case Surface.ROTATION_270: + return 270; + default: + throw new AssertionError(); + } + } + @UiThread private void startPreview(SurfaceHolder holder) throws CameraException { LOG.info("Starting preview"); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java index 9ec105ac2..0a25f9237 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementActivity.java @@ -193,8 +193,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements continueClicked = false; // FIXME #824 FragmentManager fm = getSupportFragmentManager(); - if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) { - BaseFragment f = ShowQrCodeFragment.newInstance(); + if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { + BaseFragment f = KeyAgreementFragment.newInstance(); fm.beginTransaction() .replace(R.id.fragmentContainer, f, f.getUniqueTag()) .addToBackStack(f.getUniqueTag()) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java similarity index 71% rename from briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java rename to briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java index 2c38043fb..4c4623d09 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/KeyAgreementFragment.java @@ -2,18 +2,12 @@ package org.briarproject.briar.android.keyagreement; import android.content.Context; import android.graphics.Bitmap; -import android.hardware.Camera; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.UiThread; import android.util.DisplayMetrics; -import android.view.Display; import android.view.LayoutInflater; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; -import android.view.animation.AlphaAnimation; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; @@ -41,6 +35,7 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.fragment.BaseEventFragment; import org.briarproject.briar.android.fragment.ErrorFragment; +import org.briarproject.briar.android.view.QrCodeView; import java.io.IOException; import java.nio.charset.Charset; @@ -63,10 +58,10 @@ import static org.briarproject.bramble.util.LogUtils.logException; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class ShowQrCodeFragment extends BaseEventFragment - implements QrCodeDecoder.ResultCallback { +public class KeyAgreementFragment extends BaseEventFragment + implements QrCodeDecoder.ResultCallback, QrCodeView.FullscreenListener { - static final String TAG = ShowQrCodeFragment.class.getName(); + static final String TAG = KeyAgreementFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); @@ -84,21 +79,18 @@ public class ShowQrCodeFragment extends BaseEventFragment EventBus eventBus; private CameraView cameraView; + private LinearLayout cameraOverlay; private View statusView; + private QrCodeView qrCodeView; private TextView status; - private View qrCodeContainer; - private ImageView qrCode; - private boolean fullscreen = false; private boolean gotRemotePayload; private volatile boolean gotLocalPayload; private KeyAgreementTask task; - public static ShowQrCodeFragment newInstance() { - + public static KeyAgreementFragment newInstance() { Bundle args = new Bundle(); - - ShowQrCodeFragment fragment = new ShowQrCodeFragment(); + KeyAgreementFragment fragment = new KeyAgreementFragment(); fragment.setArguments(args); return fragment; } @@ -118,7 +110,6 @@ public class ShowQrCodeFragment extends BaseEventFragment public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_keyagreement_qr, container, false); } @@ -126,45 +117,17 @@ public class ShowQrCodeFragment extends BaseEventFragment @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - cameraView = view.findViewById(R.id.camera_view); + cameraOverlay = view.findViewById(R.id.camera_overlay); statusView = view.findViewById(R.id.status_container); status = view.findViewById(R.id.connect_status); - qrCodeContainer = view.findViewById(R.id.qr_code_container); - qrCode = view.findViewById(R.id.qr_code); - ImageView fullscreenButton = view.findViewById(R.id.fullscreen_button); - fullscreenButton.setOnClickListener(v -> { - LinearLayout cameraOverlay = view.findViewById(R.id.camera_overlay); - LayoutParams statusParams, qrCodeParams; - if (fullscreen) { - // Shrink the QR code container to fill half its parent - if (cameraOverlay.getOrientation() == HORIZONTAL) { - statusParams = new LayoutParams(0, MATCH_PARENT, 1f); - qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f); - } else { - statusParams = new LayoutParams(MATCH_PARENT, 0, 1f); - qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f); - } - fullscreenButton.setImageResource( - R.drawable.ic_fullscreen_black_48dp); - } else { - // Grow the QR code container to fill its parent - statusParams = new LayoutParams(0, 0, 0f); - qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f); - fullscreenButton.setImageResource( - R.drawable.ic_fullscreen_exit_black_48dp); - } - statusView.setLayoutParams(statusParams); - qrCodeContainer.setLayoutParams(qrCodeParams); - cameraOverlay.invalidate(); - fullscreen = !fullscreen; - }); + qrCodeView = view.findViewById(R.id.qr_code_view); + qrCodeView.setFullscreenListener(this); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); cameraView.setPreviewConsumer(new QrCodeDecoder(this)); } @@ -173,30 +136,33 @@ public class ShowQrCodeFragment extends BaseEventFragment public void onStart() { super.onStart(); try { - cameraView.start(getScreenRotationDegrees()); + cameraView.start(); } catch (CameraException e) { logCameraExceptionAndFinish(e); } startListening(); } - /** - * See {@link Camera#setDisplayOrientation(int)}. - */ - private int getScreenRotationDegrees() { - Display d = getActivity().getWindowManager().getDefaultDisplay(); - switch (d.getRotation()) { - case Surface.ROTATION_0: - return 0; - case Surface.ROTATION_90: - return 90; - case Surface.ROTATION_180: - return 180; - case Surface.ROTATION_270: - return 270; - default: - throw new AssertionError(); + @Override + public void toggleFullscreen(boolean fullscreen) { + LinearLayout.LayoutParams statusParams, qrCodeParams; + if (fullscreen) { + // Grow the QR code view to fill its parent + statusParams = new LayoutParams(0, 0, 0f); + qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f); + } else { + // Shrink the QR code view to fill half its parent + if (cameraOverlay.getOrientation() == HORIZONTAL) { + statusParams = new LayoutParams(0, MATCH_PARENT, 1f); + qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f); + } else { + statusParams = new LayoutParams(MATCH_PARENT, 0, 1f); + qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f); + } } + statusView.setLayoutParams(statusParams); + qrCodeView.setLayoutParams(qrCodeParams); + cameraOverlay.invalidate(); } @Override @@ -242,7 +208,7 @@ public class ShowQrCodeFragment extends BaseEventFragment // If we've stopped the camera view, restart it if (gotRemotePayload) { try { - cameraView.start(getScreenRotationDegrees()); + cameraView.start(); } catch (CameraException e) { logCameraExceptionAndFinish(e); return; @@ -299,51 +265,10 @@ public class ShowQrCodeFragment extends BaseEventFragment KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e; keyAgreementAborted(event.didRemoteAbort()); } else if (e instanceof KeyAgreementFinishedEvent) { - runOnUiThreadUnlessDestroyed(() -> { - statusView.setVisibility(VISIBLE); - status.setText(R.string.exchanging_contact_details); - }); + keyAgreementFinished(); } } - @UiThread - private void generateBitmapQR(Payload payload) { - // Get narrowest screen dimension - Context context = getContext(); - if (context == null) return; - DisplayMetrics dm = context.getResources().getDisplayMetrics(); - new AsyncTask() { - - @Override - @Nullable - protected Bitmap doInBackground(Void... params) { - byte[] payloadBytes = payloadEncoder.encode(payload); - if (LOG.isLoggable(INFO)) { - LOG.info("Local payload is " + payloadBytes.length - + " bytes"); - } - // Use ISO 8859-1 to encode bytes directly as a string - String content = new String(payloadBytes, ISO_8859_1); - return QrCodeUtils.createQrCode(dm, content); - } - - @Override - protected void onPostExecute(@Nullable Bitmap bitmap) { - if (bitmap != null && !isDetached()) { - qrCode.setImageBitmap(bitmap); - // Simple fade-in animation - AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); - anim.setDuration(200); - qrCode.startAnimation(anim); - } - } - }.execute(); - } - - private void setQrCode(Payload localPayload) { - runOnUiThreadUnlessDestroyed(() -> generateBitmapQR(localPayload)); - } - private void keyAgreementFailed() { runOnUiThreadUnlessDestroyed(() -> { reset(); @@ -353,25 +278,25 @@ public class ShowQrCodeFragment extends BaseEventFragment }); } - private void keyAgreementWaiting() { - runOnUiThreadUnlessDestroyed( - () -> status.setText(R.string.waiting_for_contact_to_scan)); - } - private void keyAgreementStarted() { runOnUiThreadUnlessDestroyed(() -> { - qrCodeContainer.setVisibility(INVISIBLE); + qrCodeView.setVisibility(INVISIBLE); statusView.setVisibility(VISIBLE); status.setText(R.string.authenticating_with_device); }); } + private void keyAgreementWaiting() { + runOnUiThreadUnlessDestroyed( + () -> status.setText(R.string.waiting_for_contact_to_scan)); + } + private void keyAgreementAborted(boolean remoteAborted) { runOnUiThreadUnlessDestroyed(() -> { reset(); - qrCodeContainer.setVisibility(VISIBLE); + qrCodeView.setVisibility(VISIBLE); statusView.setVisibility(INVISIBLE); - status.setText(null); + status.setText(""); // TODO show abort somewhere persistent? Toast.makeText(getActivity(), remoteAborted ? R.string.connection_aborted_remote : @@ -380,6 +305,30 @@ public class ShowQrCodeFragment extends BaseEventFragment }); } + private void keyAgreementFinished() { + runOnUiThreadUnlessDestroyed(() -> { + statusView.setVisibility(VISIBLE); + status.setText(R.string.exchanging_contact_details); + }); + } + + private void setQrCode(Payload localPayload) { + Context context = getContext(); + if (context == null) return; + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + ioExecutor.execute(() -> { + byte[] payloadBytes = payloadEncoder.encode(localPayload); + if (LOG.isLoggable(INFO)) { + LOG.info("Local payload is " + payloadBytes.length + + " bytes"); + } + // Use ISO 8859-1 to encode bytes directly as a string + String content = new String(payloadBytes, ISO_8859_1); + Bitmap qrCode = QrCodeUtils.createQrCode(dm, content); + runOnUiThreadUnlessDestroyed(() -> qrCodeView.setQrCode(qrCode)); + }); + } + @Override public void handleResult(Result result) { runOnUiThreadUnlessDestroyed(() -> { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/QrCodeView.java b/briar-android/src/main/java/org/briarproject/briar/android/view/QrCodeView.java new file mode 100644 index 000000000..c4649cf4f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/QrCodeView.java @@ -0,0 +1,61 @@ +package org.briarproject.briar.android.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.animation.AlphaAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.briarproject.briar.R; + +public class QrCodeView extends FrameLayout { + + private final ImageView qrCodeImageView; + private boolean fullscreen = false; + private FullscreenListener listener; + + public QrCodeView(@NonNull Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.qr_code_view, this, true); + qrCodeImageView = findViewById(R.id.qr_code); + ImageView fullscreenButton = findViewById(R.id.fullscreen_button); + fullscreenButton.setOnClickListener(v -> { + fullscreen = !fullscreen; + if (!fullscreen) + fullscreenButton.setImageResource( + R.drawable.ic_fullscreen_black_48dp); + else + fullscreenButton.setImageResource( + R.drawable.ic_fullscreen_exit_black_48dp); + if (listener != null) + listener.toggleFullscreen(fullscreen); + } + ); + } + + public void setQrCode(Bitmap qrCode) { + qrCodeImageView.setImageBitmap(qrCode); + // Simple fade-in animation + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + qrCodeImageView.startAnimation(anim); + } + + @UiThread + public void setFullscreenListener(FullscreenListener listener) { + this.listener = listener; + } + + public interface FullscreenListener { + void toggleFullscreen(boolean isFullscreen); + } + +} diff --git a/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml b/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml deleted file mode 100644 index 9d378e5ba..000000000 --- a/briar-android/src/main/res/layout-land/fragment_keyagreement_qr.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml b/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml index d9527df8f..f992ee203 100644 --- a/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml +++ b/briar-android/src/main/res/layout/fragment_keyagreement_qr.xml @@ -1,7 +1,6 @@ @@ -43,45 +42,11 @@ tools:text="Connection failed"/> - - - - - - - - - - - + android:background="@android:color/white"/> diff --git a/briar-android/src/main/res/layout/qr_code_view.xml b/briar-android/src/main/res/layout/qr_code_view.xml new file mode 100644 index 000000000..eb690fe97 --- /dev/null +++ b/briar-android/src/main/res/layout/qr_code_view.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + +