diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 712b18d70..c066515c4 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -426,12 +426,12 @@ 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 687b37ad0..bcbb475d5 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 @@ -15,12 +15,14 @@ import org.briarproject.briar.android.blog.ReblogFragment; import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity; -import org.briarproject.briar.android.contact.ContactLinkInputActivity; +import org.briarproject.briar.android.contact.ContactAliasInputFragment; +import org.briarproject.briar.android.contact.ContactInviteInputActivity; +import org.briarproject.briar.android.contact.ContactInviteOutputActivity; import org.briarproject.briar.android.contact.ContactLinkInputFragment; -import org.briarproject.briar.android.contact.ContactLinkOutputActivity; import org.briarproject.briar.android.contact.ContactLinkOutputFragment; import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactModule; +import org.briarproject.briar.android.contact.ContactQrCodeInputFragment; import org.briarproject.briar.android.contact.ContactQrCodeOutputFragment; import org.briarproject.briar.android.contact.PendingRequestsActivity; import org.briarproject.briar.android.conversation.AliasDialogFragment; @@ -177,12 +179,14 @@ public interface ActivityComponent { void inject(UnlockActivity activity); - void inject(ContactLinkOutputActivity activity); - void inject(ContactLinkInputActivity activity); + void inject(ContactInviteOutputActivity activity); + void inject(ContactInviteInputActivity activity); void inject(PendingRequestsActivity activity); void inject(ContactLinkOutputFragment activity); void inject(ContactQrCodeOutputFragment activity); void inject(ContactLinkInputFragment activity); + void inject(ContactQrCodeInputFragment activity); + void inject(ContactAliasInputFragment activity); // Fragments void inject(AuthorNameFragment fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactAliasInputFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactAliasInputFragment.java new file mode 100644 index 000000000..c2e5d9985 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactAliasInputFragment.java @@ -0,0 +1,112 @@ +package org.briarproject.briar.android.contact; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; + +import javax.annotation.Nullable; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + +@NotNullByDefault +public class ContactAliasInputFragment extends BaseFragment + implements TextWatcher { + + private EditText contactNameInput; + private Button addButton; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + getActivity().setTitle("Enter Contact Name"); + + View v = inflater.inflate(R.layout.fragment_contact_alias_input, + container, false); + + contactNameInput = v.findViewById(R.id.contactNameInput); + contactNameInput.addTextChangedListener(this); + + addButton = v.findViewById(R.id.addButton); + addButton.setOnClickListener(view -> onAddButtonClicked()); + + return v; + } + + public static final String TAG = ContactAliasInputFragment.class.getName(); + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + updateAddButtonState(); + } + + @Override + public void afterTextChanged(Editable s) { + } + + private boolean isBriarLink(CharSequence s) { + return getActivity() != null && + ((ContactInviteInputActivity) getActivity()).isBriarLink(s); + } + + private void updateAddButtonState() { + addButton.setEnabled(contactNameInput.getText().length() > 0); + } + + private void onAddButtonClicked() { + if (getActivity() == null || getContext() == null) return;; + + ((ContactInviteInputActivity) getActivity()) + .addFakeRequest(contactNameInput.getText().toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), R.style.BriarDialogTheme_Neutral); + builder.setTitle("Contact requested"); + builder.setMessage(getString(R.string.add_contact_link_question)); + builder.setPositiveButton(R.string.yes, (dialog, which) -> { + Intent intent = new Intent(getContext(), NavDrawerActivity.class); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + finish(); + }); + builder.setNegativeButton(R.string.no, (dialog, which) -> { + startActivity( + new Intent(getContext(), ContactInviteOutputActivity.class)); + finish(); + }); + builder.show(); + } + + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteInputActivity.java similarity index 88% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteInputActivity.java index 1358a30e6..802e43c2b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteInputActivity.java @@ -32,7 +32,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; -public class ContactLinkInputActivity extends BriarActivity implements +public class ContactInviteInputActivity extends BriarActivity implements BaseFragmentListener { @Inject @@ -76,6 +76,7 @@ public class ContactLinkInputActivity extends BriarActivity implements } else if ("addContact".equals(action)) { removeFakeRequest(i.getStringExtra("name"), i.getLongExtra("timestamp", 0)); + setIntent(null); finish(); } } @@ -95,6 +96,23 @@ public class ContactLinkInputActivity extends BriarActivity implements } } + boolean isBriarLink(CharSequence s) { + String link = s.toString().trim(); + return link.matches("^(briar://)?[A-Z2-7]{64}$"); + } + + void showLink() { + showInitialFragment(ContactLinkInputFragment.newInstance(null)); + } + + void showCode() { + showInitialFragment(new ContactQrCodeInputFragment()); + } + + void showAlias() { + showNextFragment(new ContactAliasInputFragment()); + } + void addFakeRequest(String name) { long timestamp = clock.currentTimeMillis(); try { @@ -109,7 +127,7 @@ public class ContactLinkInputActivity extends BriarActivity implements long fromNow = (long) (-m * Math.log(new Random().nextDouble())); long triggerAt = elapsedRealtime() + fromNow; - Intent i = new Intent(this, ContactLinkInputActivity.class); + Intent i = new Intent(this, ContactInviteInputActivity.class); i.setAction("addContact"); i.putExtra("name", name); i.putExtra("timestamp", timestamp); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteOutputActivity.java similarity index 94% rename from briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java rename to briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteOutputActivity.java index d1e0fbf18..3c63905b3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactInviteOutputActivity.java @@ -11,7 +11,7 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener import javax.annotation.Nullable; -public class ContactLinkOutputActivity extends BriarActivity implements +public class ContactInviteOutputActivity extends BriarActivity implements BaseFragmentListener { @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputFragment.java index a927ec2d3..a26acadcd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputFragment.java @@ -11,7 +11,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; -import android.widget.Toast; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; @@ -73,9 +72,8 @@ public class ContactLinkInputFragment extends BaseFragment addButton.setOnClickListener(view -> onAddButtonClicked()); Button scanCodeButton = v.findViewById(R.id.scanCodeButton); - scanCodeButton.setOnClickListener(view -> Toast - .makeText(getContext(), "Not implemented!", Toast.LENGTH_SHORT) - .show()); + scanCodeButton.setOnClickListener(view -> + ((ContactInviteInputActivity) getActivity()).showCode()); linkInput.setText(getArguments().getString("link")); @@ -93,6 +91,7 @@ public class ContactLinkInputFragment extends BaseFragment public void injectFragment(ActivityComponent component) { component.inject(this); } + @Override public void onResume() { super.onResume(); @@ -115,17 +114,19 @@ public class ContactLinkInputFragment extends BaseFragment @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - updateAddButtonState(); + if (isBriarLink(linkInput.getText()) && getActivity() != null) { + linkInput.setText(null); + ((ContactInviteInputActivity) getActivity()).showAlias(); + } } @Override public void afterTextChanged(Editable s) { } - private boolean isBriarLink(CharSequence s) { - String link = s.toString().trim(); - return link.matches("^(briar://)?[A-Z2-7]{64}$"); + return getActivity() != null && + ((ContactInviteInputActivity) getActivity()).isBriarLink(s); } private void updateAddButtonState() { @@ -134,12 +135,14 @@ public class ContactLinkInputFragment extends BaseFragment } private void onAddButtonClicked() { - if (getActivity() == null || getContext() == null) return;; + if (getActivity() == null || getContext() == null) return; + ; - ((ContactLinkInputActivity) getActivity()) + ((ContactInviteInputActivity) getActivity()) .addFakeRequest(contactNameInput.getText().toString()); - AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), R.style.BriarDialogTheme_Neutral); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), + R.style.BriarDialogTheme_Neutral); builder.setMessage(getString(R.string.add_contact_link_question)); builder.setPositiveButton(R.string.yes, (dialog, which) -> { Intent intent = new Intent(getContext(), NavDrawerActivity.class); @@ -149,7 +152,8 @@ public class ContactLinkInputFragment extends BaseFragment }); builder.setNegativeButton(R.string.no, (dialog, which) -> { startActivity( - new Intent(getContext(), ContactLinkOutputActivity.class)); + new Intent(getContext(), + ContactInviteOutputActivity.class)); finish(); }); builder.show(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputFragment.java index 26cf6b1b0..531d740ab 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputFragment.java @@ -66,7 +66,7 @@ public class ContactLinkOutputFragment extends BaseFragment { Button showCodeButton = v.findViewById(R.id.showCodeButton); showCodeButton.setOnClickListener( - view -> ((ContactLinkOutputActivity) getActivity()).showCode()); + view -> ((ContactInviteOutputActivity) getActivity()).showCode()); return v; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java index 7502f40f0..d0e8517f5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java @@ -205,11 +205,11 @@ public class ContactListFragment extends BaseFragment implements EventListener, return false; case R.id.action_open_link: startActivity(new Intent(getContext(), - ContactLinkInputActivity.class)); + ContactInviteInputActivity.class)); return false; case R.id.action_send_link: startActivity(new Intent(getContext(), - ContactLinkOutputActivity.class)); + ContactInviteOutputActivity.class)); return false; default: return false; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeInputFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeInputFragment.java new file mode 100644 index 000000000..0d0a90c08 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeInputFragment.java @@ -0,0 +1,182 @@ +package org.briarproject.briar.android.contact; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import com.google.zxing.Result; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.fragment.BaseFragment; +import org.briarproject.briar.android.keyagreement.CameraException; +import org.briarproject.briar.android.keyagreement.CameraView; +import org.briarproject.briar.android.keyagreement.QrCodeDecoder; +import org.briarproject.briar.android.util.UiUtils; + +import javax.annotation.Nullable; + +import static android.Manifest.permission.CAMERA; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.widget.Toast.LENGTH_LONG; +import static android.widget.Toast.LENGTH_SHORT; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA; + +@NotNullByDefault +public class ContactQrCodeInputFragment extends BaseFragment + implements QrCodeDecoder.ResultCallback { + + private CameraView cameraView; + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + cameraView.setPreviewConsumer(new QrCodeDecoder(this)); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + getActivity().setTitle(R.string.open_link_title); + + View v = inflater.inflate(R.layout.fragment_contact_qr_code_input, + container, false); + + cameraView = v.findViewById(R.id.camera_view); + + + Button enterLinkButton = v.findViewById(R.id.enterLinkButton); + enterLinkButton.setOnClickListener(view -> + ((ContactInviteInputActivity) getActivity()).showLink()); + + return v; + } + + public static final String TAG = ContactQrCodeInputFragment.class.getName(); + + @Override + public void onStart() { + super.onStart(); + if (checkPermissions()) { + startCamera(); + } + } + + @Override + public void onStop() { + super.onStop(); + try { + cameraView.stop(); + } catch (CameraException e) { + e.printStackTrace(); + } + } + + private void startCamera() { + try { + cameraView.start(); + } catch (CameraException e) { + e.printStackTrace(); + Toast.makeText(getContext(), "Camera Error", LENGTH_SHORT) + .show(); + } + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + private boolean checkPermissions() { + if (ActivityCompat.checkSelfPermission(getContext(), CAMERA) != + PERMISSION_GRANTED) { + // Should we show an explanation? + if (shouldShowRequestPermissionRationale(CAMERA)) { + DialogInterface.OnClickListener continueListener = + (dialog, which) -> requestPermission(); + AlertDialog.Builder + builder = new AlertDialog.Builder(getContext(), R.style.BriarDialogTheme); + builder.setTitle(R.string.permission_camera_title); + builder.setMessage(R.string.permission_camera_request_body); + builder.setNeutralButton(R.string.continue_button, + continueListener); + builder.show(); + } else { + requestPermission(); + } + return false; + } else { + return true; + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == REQUEST_PERMISSION_CAMERA) { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && + grantResults[0] == PERMISSION_GRANTED) { + startCamera(); + } else { + if (!shouldShowRequestPermissionRationale(CAMERA)) { + // The user has permanently denied the request + AlertDialog.Builder + builder = new AlertDialog.Builder(getContext(), + R.style.BriarDialogTheme); + builder.setTitle(R.string.permission_camera_title); + builder.setMessage(R.string.permission_camera_denied_body); + builder.setPositiveButton(R.string.ok, + UiUtils.getGoToSettingsListener(getContext())); + builder.setNegativeButton(R.string.cancel, + (dialog, which) -> showLink()); + builder.show(); + } else { + Toast.makeText(getContext(), + R.string.permission_camera_denied_toast, + LENGTH_LONG).show(); + showLink(); + } + } + } + } + + private void requestPermission() { + requestPermissions(new String[] {CAMERA}, REQUEST_PERMISSION_CAMERA); + } + + private void showLink() { + if (getActivity() != null) + ((ContactInviteInputActivity) getActivity()).showLink(); + } + + @Override + public void handleResult(Result result) { + Log.e("TEST", result.toString()); + if (getActivity() != null && + ((ContactInviteInputActivity) getActivity()) + .isBriarLink(result.getText())) { + ((ContactInviteInputActivity) getActivity()).showAlias(); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeOutputFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeOutputFragment.java index 2c8e56203..d92fd5fb5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeOutputFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactQrCodeOutputFragment.java @@ -48,7 +48,7 @@ public class ContactQrCodeOutputFragment extends BaseFragment Button showLinkButton = v.findViewById(R.id.showLinkButton); showLinkButton.setOnClickListener( - view -> ((ContactLinkOutputActivity) getActivity()).showLink()); + view -> ((ContactInviteOutputActivity) getActivity()).showLink()); return v; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java index 0bff14ed7..cce6bf207 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/CameraException.java @@ -2,7 +2,7 @@ package org.briarproject.briar.android.keyagreement; import java.io.IOException; -class CameraException extends IOException { +public class CameraException extends IOException { CameraException(String message) { super(message); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java index 11735caeb..b6484cd83 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/PreviewConsumer.java @@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @SuppressWarnings("deprecation") @NotNullByDefault -interface PreviewConsumer { +public interface PreviewConsumer { @UiThread void start(Camera camera, int cameraIndex); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java index 7b018be03..f031ba24b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/QrCodeDecoder.java @@ -29,7 +29,7 @@ import static java.util.logging.Level.WARNING; @SuppressWarnings("deprecation") @MethodsNotNullByDefault @ParametersNotNullByDefault -class QrCodeDecoder implements PreviewConsumer, PreviewCallback { +public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private static final Logger LOG = Logger.getLogger(QrCodeDecoder.class.getName()); @@ -40,7 +40,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private Camera camera = null; private int cameraIndex = 0; - QrCodeDecoder(ResultCallback callback) { + public QrCodeDecoder(ResultCallback callback) { this.callback = callback; } @@ -141,7 +141,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback { } @NotNullByDefault - interface ResultCallback { + public interface ResultCallback { void handleResult(Result result); } diff --git a/briar-android/src/main/res/drawable/ic_content_paste.xml b/briar-android/src/main/res/drawable/ic_content_paste.xml new file mode 100644 index 000000000..faac5cbb2 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_content_paste.xml @@ -0,0 +1,5 @@ + + + diff --git a/briar-android/src/main/res/layout/fragment_contact_alias_input.xml b/briar-android/src/main/res/layout/fragment_contact_alias_input.xml new file mode 100644 index 000000000..b6d30b762 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_contact_alias_input.xml @@ -0,0 +1,37 @@ + + + + + +