Merge branch 'factor_out_keyagreement_ui-theme' into 'master'

Factor out keyagreement ui theme

See merge request briar/briar!835
This commit is contained in:
akwizgran
2018-06-21 11:38:49 +00:00
11 changed files with 385 additions and 379 deletions

View File

@@ -300,7 +300,7 @@
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.keyagreement.KeyAgreementActivity" android:name="org.briarproject.briar.android.keyagreement.ContactExchangeActivity"
android:label="@string/add_contact_title" android:label="@string/add_contact_title"
android:theme="@style/BriarTheme.NoActionBar" android:theme="@style/BriarTheme.NoActionBar"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"> android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">

View File

@@ -26,9 +26,10 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.introduction.ContactChooserFragment; import org.briarproject.briar.android.introduction.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment; 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.IntroFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; 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.AuthorNameFragment;
import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.ChangePasswordActivity;
import org.briarproject.briar.android.login.DozeFragment; import org.briarproject.briar.android.login.DozeFragment;
@@ -100,6 +101,8 @@ public interface ActivityComponent {
void inject(PanicPreferencesActivity activity); void inject(PanicPreferencesActivity activity);
void inject(ContactExchangeActivity activity);
void inject(KeyAgreementActivity activity); void inject(KeyAgreementActivity activity);
void inject(ConversationActivity activity); void inject(ConversationActivity activity);
@@ -185,7 +188,7 @@ public interface ActivityComponent {
void inject(IntroFragment fragment); void inject(IntroFragment fragment);
void inject(ShowQrCodeFragment fragment); void inject(KeyAgreementFragment fragment);
void inject(ContactChooserFragment fragment); void inject(ContactChooserFragment fragment);

View File

@@ -33,7 +33,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.BaseMessageHeader; import org.briarproject.briar.api.client.BaseMessageHeader;
@@ -165,7 +165,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_add_contact: case R.id.action_add_contact:
Intent intent = Intent intent =
new Intent(getContext(), KeyAgreementActivity.class); new Intent(getContext(), ContactExchangeActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
default: default:

View File

@@ -9,10 +9,12 @@ import android.hardware.Camera.Size;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Display;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -21,6 +23,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.logging.Logger; 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_BACK;
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT; import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF; import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
@@ -97,7 +100,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
} }
@UiThread @UiThread
public void start(int rotationDegrees) throws CameraException { public void start() throws CameraException {
LOG.info("Opening camera"); LOG.info("Opening camera");
try { try {
int cameras = Camera.getNumberOfCameras(); int cameras = Camera.getNumberOfCameras();
@@ -122,7 +125,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new CameraException(e); throw new CameraException(e);
} }
setDisplayOrientation(rotationDegrees); setDisplayOrientation(getScreenRotationDegrees());
// Use barcode scene mode if it's available // Use barcode scene mode if it's available
Parameters params = camera.getParameters(); Parameters params = camera.getParameters();
params = setSceneMode(camera, params); params = setSceneMode(camera, params);
@@ -157,6 +160,27 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
camera = null; 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 @UiThread
private void startPreview(SurfaceHolder holder) throws CameraException { private void startPreview(SurfaceHolder holder) throws CameraException {
LOG.info("Starting preview"); LOG.info("Starting preview");

View File

@@ -0,0 +1,144 @@
package org.briarproject.briar.android.keyagreement;
import android.os.Bundle;
import android.support.annotation.UiThread;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.R.string;
import org.briarproject.briar.android.activity.ActivityComponent;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactExchangeActivity extends KeyAgreementActivity implements
ContactExchangeListener {
private static final Logger LOG =
Logger.getLogger(ContactExchangeActivity.class.getName());
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactExchangeTask contactExchangeTask;
@Inject
volatile IdentityManager identityManager;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
getSupportActionBar().setTitle(string.add_contact_title);
}
private void startContactExchange(KeyAgreementResult result) {
runOnDbThread(() -> {
LocalAuthor localAuthor;
// Load the local pseudonym
try {
localAuthor = identityManager.getLocalAuthor();
} catch (DbException e) {
logException(LOG, WARNING, e);
contactExchangeFailed();
return;
}
// Exchange contact details
contactExchangeTask.startExchange(ContactExchangeActivity.this,
localAuthor, result.getMasterKey(),
result.getConnection(), result.getTransportId(),
result.wasAlice());
});
}
@Override
public void contactExchangeSucceeded(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(string.contact_added_toast);
String text = String.format(format, contactName);
Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
.show();
supportFinishAfterTransition();
});
}
@Override
public void duplicateContact(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(string.contact_already_exists);
String text = String.format(format, contactName);
Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
.show();
finish();
});
}
@Override
public void contactExchangeFailed() {
runOnUiThreadUnlessDestroyed(() -> {
Toast.makeText(ContactExchangeActivity.this,
string.contact_exchange_failed, LENGTH_LONG).show();
finish();
});
}
@UiThread
@Override
public void keyAgreementFailed() {
// TODO show failure somewhere persistent?
Toast.makeText(this, R.string.connection_failed,
LENGTH_LONG).show();
}
@UiThread
@Override
public String keyAgreementWaiting() {
return getString(R.string.waiting_for_contact_to_scan);
}
@UiThread
@Override
public String keyAgreementStarted() {
return getString(R.string.authenticating_with_device);
}
@UiThread
@Override
public String keyAgreementAborted(boolean remoteAborted) {
// TODO show abort somewhere persistent?
Toast.makeText(this,
remoteAborted ? R.string.connection_aborted_remote :
R.string.connection_aborted_local, LENGTH_LONG)
.show();
return null;
}
@UiThread
@Override
public String keyAgreementFinished(KeyAgreementResult result) {
startContactExchange(result);
return getString(string.exchanging_contact_details);
}
}

View File

@@ -16,17 +16,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
@@ -53,16 +43,14 @@ import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class KeyAgreementActivity extends BriarActivity implements public abstract class KeyAgreementActivity extends BriarActivity implements
BaseFragmentListener, IntroScreenSeenListener, EventListener, BaseFragmentListener, IntroScreenSeenListener,
ContactExchangeListener { KeyAgreementFragment.KeyAgreementEventListener {
private enum BluetoothState { private enum BluetoothState {
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
@@ -74,12 +62,6 @@ public class KeyAgreementActivity extends BriarActivity implements
@Inject @Inject
EventBus eventBus; EventBus eventBus;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactExchangeTask contactExchangeTask;
@Inject
volatile IdentityManager identityManager;
private boolean isResumed = false, enableWasRequested = false; private boolean isResumed = false, enableWasRequested = false;
private boolean continueClicked, gotCameraPermission; private boolean continueClicked, gotCameraPermission;
private BluetoothState bluetoothState = BluetoothState.UNKNOWN; private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
@@ -95,13 +77,9 @@ public class KeyAgreementActivity extends BriarActivity implements
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_fragment_container_toolbar); setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.add_contact_title);
if (state == null) { if (state == null) {
showInitialFragment(IntroFragment.newInstance()); showInitialFragment(IntroFragment.newInstance());
} }
@@ -116,18 +94,6 @@ public class KeyAgreementActivity extends BriarActivity implements
if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver);
} }
@Override
public void onStart() {
super.onStart();
eventBus.addListener(this);
}
@Override
protected void onStop() {
super.onStop();
eventBus.removeListener(this);
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@@ -148,7 +114,7 @@ public class KeyAgreementActivity extends BriarActivity implements
if (canShowQrCodeFragment()) showQrCodeFragment(); if (canShowQrCodeFragment()) showQrCodeFragment();
} }
boolean canShowQrCodeFragment() { private boolean canShowQrCodeFragment() {
return isResumed && continueClicked return isResumed && continueClicked
&& (SDK_INT < 23 || gotCameraPermission) && (SDK_INT < 23 || gotCameraPermission)
&& bluetoothState != BluetoothState.UNKNOWN && bluetoothState != BluetoothState.UNKNOWN
@@ -207,10 +173,11 @@ public class KeyAgreementActivity extends BriarActivity implements
} }
private void showQrCodeFragment() { private void showQrCodeFragment() {
continueClicked = false;
// FIXME #824 // FIXME #824
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) { if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
BaseFragment f = ShowQrCodeFragment.newInstance(); BaseFragment f = KeyAgreementFragment.newInstance(this);
fm.beginTransaction() fm.beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag()) .replace(R.id.fragmentContainer, f, f.getUniqueTag())
.addToBackStack(f.getUniqueTag()) .addToBackStack(f.getUniqueTag())
@@ -280,69 +247,6 @@ public class KeyAgreementActivity extends BriarActivity implements
} }
} }
@Override
public void eventOccurred(Event e) {
if (e instanceof KeyAgreementFinishedEvent) {
KeyAgreementFinishedEvent event = (KeyAgreementFinishedEvent) e;
keyAgreementFinished(event.getResult());
}
}
private void keyAgreementFinished(KeyAgreementResult result) {
runOnUiThreadUnlessDestroyed(() -> startContactExchange(result));
}
private void startContactExchange(KeyAgreementResult result) {
runOnDbThread(() -> {
LocalAuthor localAuthor;
// Load the local pseudonym
try {
localAuthor = identityManager.getLocalAuthor();
} catch (DbException e) {
logException(LOG, WARNING, e);
contactExchangeFailed();
return;
}
// Exchange contact details
contactExchangeTask.startExchange(KeyAgreementActivity.this,
localAuthor, result.getMasterKey(),
result.getConnection(), result.getTransportId(),
result.wasAlice());
});
}
@Override
public void contactExchangeSucceeded(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(string.contact_added_toast);
String text = String.format(format, contactName);
Toast.makeText(KeyAgreementActivity.this, text, LENGTH_LONG).show();
supportFinishAfterTransition();
});
}
@Override
public void duplicateContact(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(string.contact_already_exists);
String text = String.format(format, contactName);
Toast.makeText(KeyAgreementActivity.this, text, LENGTH_LONG).show();
finish();
});
}
@Override
public void contactExchangeFailed() {
runOnUiThreadUnlessDestroyed(() -> {
Toast.makeText(KeyAgreementActivity.this,
string.contact_exchange_failed, LENGTH_LONG).show();
finish();
});
}
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override @Override

View File

@@ -2,18 +2,12 @@ package org.briarproject.briar.android.keyagreement;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.hardware.Camera;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams; import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView; import android.widget.TextView;
@@ -24,6 +18,7 @@ import com.google.zxing.Result;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
@@ -36,11 +31,13 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseEventFragment; import org.briarproject.briar.android.fragment.BaseEventFragment;
import org.briarproject.briar.android.fragment.ErrorFragment; import org.briarproject.briar.android.fragment.ErrorFragment;
import org.briarproject.briar.android.view.QrCodeView;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -63,10 +60,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ShowQrCodeFragment extends BaseEventFragment public class KeyAgreementFragment extends BaseEventFragment
implements QrCodeDecoder.ResultCallback { 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 Logger LOG = Logger.getLogger(TAG);
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
@@ -84,21 +81,21 @@ public class ShowQrCodeFragment extends BaseEventFragment
EventBus eventBus; EventBus eventBus;
private CameraView cameraView; private CameraView cameraView;
private LinearLayout cameraOverlay;
private View statusView; private View statusView;
private QrCodeView qrCodeView;
private TextView status; private TextView status;
private View qrCodeContainer;
private ImageView qrCode;
private boolean fullscreen = false;
private boolean gotRemotePayload; private boolean gotRemotePayload;
private volatile boolean gotLocalPayload; private volatile boolean gotLocalPayload;
private KeyAgreementTask task; private KeyAgreementTask task;
private KeyAgreementEventListener listener;
public static ShowQrCodeFragment newInstance() { public static KeyAgreementFragment newInstance(
KeyAgreementEventListener listener) {
Bundle args = new Bundle(); Bundle args = new Bundle();
KeyAgreementFragment fragment = new KeyAgreementFragment();
ShowQrCodeFragment fragment = new ShowQrCodeFragment(); fragment.listener = listener;
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@@ -118,7 +115,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_keyagreement_qr, container, return inflater.inflate(R.layout.fragment_keyagreement_qr, container,
false); false);
} }
@@ -126,45 +122,17 @@ public class ShowQrCodeFragment extends BaseEventFragment
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
cameraView = view.findViewById(R.id.camera_view); cameraView = view.findViewById(R.id.camera_view);
cameraOverlay = view.findViewById(R.id.camera_overlay);
statusView = view.findViewById(R.id.status_container); statusView = view.findViewById(R.id.status_container);
status = view.findViewById(R.id.connect_status); status = view.findViewById(R.id.connect_status);
qrCodeContainer = view.findViewById(R.id.qr_code_container); qrCodeView = view.findViewById(R.id.qr_code_view);
qrCode = view.findViewById(R.id.qr_code); qrCodeView.setFullscreenListener(this);
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;
});
} }
@Override @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
cameraView.setPreviewConsumer(new QrCodeDecoder(this)); cameraView.setPreviewConsumer(new QrCodeDecoder(this));
} }
@@ -173,30 +141,33 @@ public class ShowQrCodeFragment extends BaseEventFragment
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
try { try {
cameraView.start(getScreenRotationDegrees()); cameraView.start();
} catch (CameraException e) { } catch (CameraException e) {
logCameraExceptionAndFinish(e); logCameraExceptionAndFinish(e);
} }
startListening(); startListening();
} }
/** @Override
* See {@link Camera#setDisplayOrientation(int)}. public void setFullscreen(boolean fullscreen) {
*/ LinearLayout.LayoutParams statusParams, qrCodeParams;
private int getScreenRotationDegrees() { if (fullscreen) {
Display d = getActivity().getWindowManager().getDefaultDisplay(); // Grow the QR code view to fill its parent
switch (d.getRotation()) { statusParams = new LayoutParams(0, 0, 0f);
case Surface.ROTATION_0: qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
return 0; } else {
case Surface.ROTATION_90: // Shrink the QR code view to fill half its parent
return 90; if (cameraOverlay.getOrientation() == HORIZONTAL) {
case Surface.ROTATION_180: statusParams = new LayoutParams(0, MATCH_PARENT, 1f);
return 180; qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f);
case Surface.ROTATION_270: } else {
return 270; statusParams = new LayoutParams(MATCH_PARENT, 0, 1f);
default: qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f);
throw new AssertionError(); }
} }
statusView.setLayoutParams(statusParams);
qrCodeView.setLayoutParams(qrCodeParams);
cameraOverlay.invalidate();
} }
@Override @Override
@@ -242,7 +213,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
// If we've stopped the camera view, restart it // If we've stopped the camera view, restart it
if (gotRemotePayload) { if (gotRemotePayload) {
try { try {
cameraView.start(getScreenRotationDegrees()); cameraView.start();
} catch (CameraException e) { } catch (CameraException e) {
logCameraExceptionAndFinish(e); logCameraExceptionAndFinish(e);
return; return;
@@ -299,84 +270,60 @@ public class ShowQrCodeFragment extends BaseEventFragment
KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e; KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e;
keyAgreementAborted(event.didRemoteAbort()); keyAgreementAborted(event.didRemoteAbort());
} else if (e instanceof KeyAgreementFinishedEvent) { } else if (e instanceof KeyAgreementFinishedEvent) {
runOnUiThreadUnlessDestroyed(() -> { keyAgreementFinished(((KeyAgreementFinishedEvent) e).getResult());
statusView.setVisibility(VISIBLE);
status.setText(R.string.exchanging_contact_details);
});
} }
} }
@UiThread
private void generateBitmapQR(Payload payload) {
// Get narrowest screen dimension
Context context = getContext();
if (context == null) return;
DisplayMetrics dm = context.getResources().getDisplayMetrics();
new AsyncTask<Void, Void, Bitmap>() {
@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() { private void keyAgreementFailed() {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
reset(); reset();
// TODO show failure somewhere persistent? listener.keyAgreementFailed();
Toast.makeText(getActivity(), R.string.connection_failed,
LENGTH_LONG).show();
}); });
} }
private void keyAgreementWaiting() { private void keyAgreementWaiting() {
runOnUiThreadUnlessDestroyed( runOnUiThreadUnlessDestroyed(
() -> status.setText(R.string.waiting_for_contact_to_scan)); () -> status.setText(listener.keyAgreementWaiting()));
} }
private void keyAgreementStarted() { private void keyAgreementStarted() {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
qrCodeContainer.setVisibility(INVISIBLE); qrCodeView.setVisibility(INVISIBLE);
statusView.setVisibility(VISIBLE); statusView.setVisibility(VISIBLE);
status.setText(R.string.authenticating_with_device); status.setText(listener.keyAgreementStarted());
}); });
} }
private void keyAgreementAborted(boolean remoteAborted) { private void keyAgreementAborted(boolean remoteAborted) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
reset(); reset();
qrCodeContainer.setVisibility(VISIBLE); qrCodeView.setVisibility(VISIBLE);
statusView.setVisibility(INVISIBLE); statusView.setVisibility(INVISIBLE);
status.setText(null); status.setText(listener.keyAgreementAborted(remoteAborted));
// TODO show abort somewhere persistent? });
Toast.makeText(getActivity(), }
remoteAborted ? R.string.connection_aborted_remote :
R.string.connection_aborted_local, LENGTH_LONG) private void keyAgreementFinished(KeyAgreementResult result) {
.show(); runOnUiThreadUnlessDestroyed(() -> {
statusView.setVisibility(VISIBLE);
status.setText(listener.keyAgreementFinished(result));
});
}
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));
}); });
} }
@@ -394,4 +341,31 @@ public class ShowQrCodeFragment extends BaseEventFragment
protected void finish() { protected void finish() {
getActivity().getSupportFragmentManager().popBackStack(); getActivity().getSupportFragmentManager().popBackStack();
} }
@NotNullByDefault
interface KeyAgreementEventListener {
@UiThread
void keyAgreementFailed();
// Should return a string to be displayed as status.
@UiThread
@Nullable
String keyAgreementWaiting();
// Should return a string to be displayed as status.
@UiThread
@Nullable
String keyAgreementStarted();
// Should return a string to be displayed as status.
@UiThread
@Nullable
String keyAgreementAborted(boolean remoteAborted);
// Should return a string to be displayed as status.
@UiThread
@Nullable
String keyAgreementFinished(KeyAgreementResult result);
}
} }

View File

@@ -0,0 +1,63 @@
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.setFullscreen(fullscreen);
}
);
}
@UiThread
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 setFullscreen(boolean fullscreen);
}
}

View File

@@ -1,110 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.briarproject.briar.android.keyagreement.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/camera_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/status_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/background_light"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/margin_medium"
android:visibility="invisible">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="@dimen/margin_large"
tools:text="Connection failed"/>
</LinearLayout>
<FrameLayout
android:id="@+id/qr_code_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/white">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:contentDescription="@string/qr_code"
android:scaleType="fitCenter"/>
<ImageView
android:id="@+id/fullscreen_button"
android:background="?selectableItemBackground"
android:src="@drawable/ic_fullscreen_black_48dp"
android:alpha="0.54"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:contentDescription="@string/show_qr_code_fullscreen"/>
</RelativeLayout>
</FrameLayout>
</LinearLayout>
<RelativeLayout
android:id="@+id/container_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:visibility="invisible">
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/title_progress_bar"
android:layout_centerHorizontal="true"/>
<TextView
android:id="@+id/title_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:paddingTop="@dimen/margin_large"
tools:text="@string/waiting_for_contact_to_scan"/>
</RelativeLayout>
</FrameLayout>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -43,45 +42,11 @@
tools:text="Connection failed"/> tools:text="Connection failed"/>
</LinearLayout> </LinearLayout>
<FrameLayout <org.briarproject.briar.android.view.QrCodeView
android:id="@+id/qr_code_container" android:id="@+id/qr_code_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@android:color/white"> android:background="@android:color/white"/>
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:contentDescription="@string/qr_code"
android:scaleType="fitCenter"
tools:src="@drawable/startup_lock"/>
<ImageView
android:id="@+id/fullscreen_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_margin="@dimen/margin_small"
android:alpha="0.54"
android:background="?selectableItemBackground"
android:contentDescription="@string/show_qr_code_fullscreen"
android:src="@drawable/ic_fullscreen_black_48dp"/>
</RelativeLayout>
</FrameLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="@layout/fragment_keyagreement_qr">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/qr_code"
android:scaleType="fitCenter"/>
<ImageView
android:id="@+id/fullscreen_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:alpha="0.54"
android:background="?selectableItemBackground"
android:contentDescription="@string/show_qr_code_fullscreen"
android:src="@drawable/ic_fullscreen_black_48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
</merge>