Rewrote Android invitation wizard to use Views instead of Activities.

This commit is contained in:
akwizgran
2012-11-12 16:39:01 +00:00
parent 538c3e1b08
commit 8fffc93bbc
48 changed files with 843 additions and 844 deletions

View File

@@ -32,35 +32,7 @@
</intent-filter>
</activity>
<activity
android:name=".android.invitation.NetworkSetupActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.InvitationCodeActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.ConnectionActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.ConnectionFailedActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.ConfirmationCodeActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.WaitForContactActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.CodesDoNotMatchActivity"
android:label="@string/add_a_contact" >
</activity>
<activity
android:name=".android.invitation.ContactAddedActivity"
android:name=".android.invitation.AddContactActivity"
android:label="@string/add_a_contact" >
</activity>
</application>

View File

@@ -3,10 +3,10 @@
<string-array name="roboguice_modules">
<item>net.sf.briar.android.AndroidModule</item>
<item>net.sf.briar.android.helloworld.HelloWorldModule</item>
<item>net.sf.briar.android.invitation.AndroidInvitationModule</item>
<item>net.sf.briar.clock.ClockModule</item>
<item>net.sf.briar.crypto.CryptoModule</item>
<item>net.sf.briar.db.DatabaseModule</item>
<item>net.sf.briar.invitation.InvitationModule</item>
<item>net.sf.briar.lifecycle.LifecycleModule</item>
<item>net.sf.briar.plugins.PluginsModule</item>
<item>net.sf.briar.protocol.ProtocolModule</item>

View File

@@ -9,7 +9,7 @@ import static java.util.logging.Level.INFO;
import java.util.logging.Logger;
import net.sf.briar.R;
import net.sf.briar.android.invitation.NetworkSetupActivity;
import net.sf.briar.android.invitation.AddContactActivity;
import roboguice.activity.RoboActivity;
import android.content.Intent;
import android.os.Bundle;
@@ -66,6 +66,6 @@ implements OnClickListener {
}
public void onClick(View view) {
startActivity(new Intent(this, NetworkSetupActivity.class));
startActivity(new Intent(this, AddContactActivity.class));
}
}

View File

@@ -0,0 +1,128 @@
package net.sf.briar.android.invitation;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.invitation.ConfirmationCallback;
import net.sf.briar.api.invitation.ConnectionCallback;
import net.sf.briar.api.invitation.InvitationManager;
import roboguice.activity.RoboActivity;
import com.google.inject.Inject;
public class AddContactActivity extends RoboActivity
implements ConnectionCallback, ConfirmationCallback {
@Inject private CryptoComponent crypto;
@Inject private InvitationManager invitationManager;
// All of the following must be accessed on the UI thread
private AddContactView view = null;
private String networkName = null;
private boolean useBluetooth = false;
private int localInvitationCode = -1;
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
private ConfirmationCallback callback = null;
private boolean localMatched = false;
private boolean remoteCompared = false, remoteMatched = false;
@Override
public void onResume() {
super.onResume();
if(view == null) setView(new NetworkSetupView(this));
else view.populate();
}
void setView(AddContactView view) {
this.view = view;
view.init(this);
setContentView(view);
}
void setNetworkName(String networkName) {
this.networkName = networkName;
}
String getNetworkName() {
return networkName;
}
void setUseBluetooth(boolean useBluetooth) {
this.useBluetooth = useBluetooth;
}
boolean getUseBluetooth() {
return useBluetooth;
}
int generateLocalInvitationCode() {
localInvitationCode = crypto.generateInvitationCode();
return localInvitationCode;
}
int getLocalInvitationCode() {
return localInvitationCode;
}
void remoteInvitationCodeEntered(int code) {
setView(new ConnectionView(this));
localMatched = remoteCompared = remoteMatched = false;
invitationManager.connect(localInvitationCode, code, this);
}
int getLocalConfirmationCode() {
return localConfirmationCode;
}
void remoteConfirmationCodeEntered(int code) {
if(code == remoteConfirmationCode) {
localMatched = true;
if(remoteMatched) setView(new ContactAddedView(this));
else if(remoteCompared) setView(new CodesDoNotMatchView(this));
else setView(new WaitForContactView(this));
callback.codesMatch();
} else {
setView(new CodesDoNotMatchView(this));
callback.codesDoNotMatch();
}
}
public void connectionEstablished(final int localCode, final int remoteCode,
final ConfirmationCallback c) {
runOnUiThread(new Runnable() {
public void run() {
localConfirmationCode = localCode;
remoteConfirmationCode = remoteCode;
callback = c;
setView(new ConfirmationCodeView(AddContactActivity.this));
}
});
}
public void connectionNotEstablished() {
runOnUiThread(new Runnable() {
public void run() {
setView(new ConnectionFailedView(AddContactActivity.this));
}
});
}
public void codesMatch() {
runOnUiThread(new Runnable() {
public void run() {
remoteCompared = true;
remoteMatched = true;
if(localMatched)
setView(new ContactAddedView(AddContactActivity.this));
}
});
}
public void codesDoNotMatch() {
runOnUiThread(new Runnable() {
public void run() {
remoteCompared = true;
if(localMatched)
setView(new CodesDoNotMatchView(AddContactActivity.this));
}
});
}
}

View File

@@ -0,0 +1,25 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.content.Context;
import android.widget.LinearLayout;
abstract class AddContactView extends LinearLayout {
protected AddContactActivity container = null;
AddContactView(Context context) {
super(context);
}
void init(AddContactActivity container) {
this.container = container;
setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
setOrientation(VERTICAL);
setGravity(CENTER_HORIZONTAL);
populate();
}
abstract void populate();
}

View File

@@ -1,14 +0,0 @@
package net.sf.briar.android.invitation;
import javax.inject.Singleton;
import com.google.inject.AbstractModule;
public class AndroidInvitationModule extends AbstractModule {
@Override
protected void configure() {
bind(InvitationManager.class).to(InvitationManagerImpl.class).in(
Singleton.class);
}
}

View File

@@ -2,5 +2,5 @@ package net.sf.briar.android.invitation;
interface CodeEntryListener {
void codeEntered(String code);
void codeEntered(int remoteCode);
}

View File

@@ -3,6 +3,7 @@ package net.sf.briar.android.invitation;
import static android.text.InputType.TYPE_CLASS_NUMBER;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static net.sf.briar.api.plugins.InvitationConstants.MAX_CODE;
import net.sf.briar.R;
import android.content.Context;
import android.view.KeyEvent;
@@ -53,6 +54,7 @@ OnEditorActionListener, OnClickListener {
codeEntry.setMaxEms(5);
codeEntry.setMaxLines(1);
codeEntry.setInputType(TYPE_CLASS_NUMBER);
codeEntry.requestFocus();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
@@ -63,16 +65,24 @@ OnEditorActionListener, OnClickListener {
}
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
validateAndReturnCode();
if(!validateAndReturnCode()) codeEntry.setText("");
return true;
}
public void onClick(View view) {
validateAndReturnCode();
if(!validateAndReturnCode()) codeEntry.setText("");
}
private void validateAndReturnCode() {
CharSequence code = codeEntry.getText();
if(code.length() == 6) listener.codeEntered(code.toString());
private boolean validateAndReturnCode() {
String remoteCodeString = codeEntry.getText().toString();
int remoteCode;
try {
remoteCode = Integer.valueOf(remoteCodeString);
} catch(NumberFormatException e) {
return false;
}
if(remoteCode < 0 || remoteCode > MAX_CODE) return false;
listener.codeEntered(remoteCode);
return true;
}
}

View File

@@ -1,69 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class CodesDoNotMatchActivity extends Activity
implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(this);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.alerts_and_states_error);
innerLayout.addView(icon);
TextView failed = new TextView(this);
failed.setTextSize(20);
failed.setText(R.string.codes_do_not_match);
innerLayout.addView(failed);
layout.addView(innerLayout);
TextView interfering = new TextView(this);
interfering.setGravity(CENTER_HORIZONTAL);
interfering.setPadding(0, 0, 0, 10);
interfering.setText(R.string.interfering);
layout.addView(interfering);
Button tryAgain = new Button(this);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
tryAgain.setLayoutParams(lp);
tryAgain.setText(R.string.try_again_button);
tryAgain.setOnClickListener(this);
layout.addView(tryAgain);
setContentView(layout);
}
public void onClick(View view) {
Intent intent = new Intent(this, InvitationCodeActivity.class);
intent.putExtras(getIntent().getExtras());
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,58 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import net.sf.briar.R;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class CodesDoNotMatchView extends AddContactView
implements OnClickListener {
CodesDoNotMatchView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(ctx);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.alerts_and_states_error);
innerLayout.addView(icon);
TextView failed = new TextView(ctx);
failed.setTextSize(20);
failed.setText(R.string.codes_do_not_match);
innerLayout.addView(failed);
addView(innerLayout);
TextView interfering = new TextView(ctx);
interfering.setGravity(CENTER_HORIZONTAL);
interfering.setPadding(0, 0, 0, 10);
interfering.setText(R.string.interfering);
addView(interfering);
Button tryAgain = new Button(ctx);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
tryAgain.setLayoutParams(lp);
tryAgain.setText(R.string.try_again_button);
tryAgain.setOnClickListener(this);
addView(tryAgain);
}
public void onClick(View view) {
// Try again
container.setView(new NetworkSetupView(container));
}
}

View File

@@ -1,79 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import roboguice.activity.RoboActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.inject.Inject;
public class ConfirmationCodeActivity extends RoboActivity
implements CodeEntryListener {
@Inject private InvitationManager manager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(this);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.navigation_accept);
innerLayout.addView(icon);
TextView connected = new TextView(this);
connected.setTextSize(20);
connected.setText(R.string.connected_to_contact);
innerLayout.addView(connected);
layout.addView(innerLayout);
TextView yourCode = new TextView(this);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_confirmation_code);
layout.addView(yourCode);
TextView code = new TextView(this);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
code.setText(manager.getLocalConfirmationCode());
layout.addView(code);
CodeEntryWidget codeEntry = new CodeEntryWidget(this);
Resources res = getResources();
codeEntry.init(this, res.getString(R.string.enter_confirmation_code));
layout.addView(codeEntry);
setContentView(layout);
}
public void codeEntered(String code) {
if(code.equals(manager.getRemoteConfirmationCode())) {
Intent intent = new Intent(this, WaitForContactActivity.class);
intent.putExtras(getIntent().getExtras());
startActivity(intent);
} else {
Intent intent = new Intent(this, CodesDoNotMatchActivity.class);
intent.putExtras(getIntent().getExtras());
startActivity(intent);
}
finish();
}
}

View File

@@ -0,0 +1,58 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import android.content.Context;
import android.content.res.Resources;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ConfirmationCodeView extends AddContactView
implements CodeEntryListener {
ConfirmationCodeView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(ctx);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.navigation_accept);
innerLayout.addView(icon);
TextView connected = new TextView(ctx);
connected.setTextSize(20);
connected.setText(R.string.connected_to_contact);
innerLayout.addView(connected);
addView(innerLayout);
TextView yourCode = new TextView(ctx);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_confirmation_code);
addView(yourCode);
TextView code = new TextView(ctx);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
int localCode = container.getLocalConfirmationCode();
code.setText(String.format("%06d", localCode));
addView(code);
CodeEntryWidget codeEntry = new CodeEntryWidget(ctx);
Resources res = getResources();
codeEntry.init(this, res.getString(R.string.enter_confirmation_code));
addView(codeEntry);
}
public void codeEntered(int remoteCode) {
container.remoteConfirmationCodeEntered(remoteCode);
}
}

View File

@@ -1,8 +0,0 @@
package net.sf.briar.android.invitation;
interface ConfirmationListener {
void confirmationReceived();
void confirmationNotReceived();
}

View File

@@ -1,113 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import roboguice.activity.RoboActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.inject.Inject;
public class ConnectionActivity extends RoboActivity
implements ConnectionListener {
@Inject private InvitationManager manager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
Bundle b = getIntent().getExtras();
String networkName = b.getString(
"net.sf.briar.android.invitation.NETWORK_NAME");
boolean useBluetooth = b.getBoolean(
"net.sf.briar.android.invitation.USE_BLUETOOTH");
TextView yourCode = new TextView(this);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_invitation_code);
layout.addView(yourCode);
TextView code = new TextView(this);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
code.setText(manager.getLocalInvitationCode());
layout.addView(code);
if(networkName != null) {
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(this);
progress.setIndeterminate(true);
progress.setPadding(0, 10, 10, 0);
innerLayout.addView(progress);
TextView connecting = new TextView(this);
Resources res = getResources();
String connectingVia = res.getString(R.string.connecting_wifi);
connecting.setText(String.format(connectingVia, networkName));
innerLayout.addView(connecting);
layout.addView(innerLayout);
manager.startWifiConnectionWorker(this);
}
if(useBluetooth) {
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(this);
progress.setPadding(0, 10, 10, 0);
progress.setIndeterminate(true);
innerLayout.addView(progress);
TextView connecting = new TextView(this);
connecting.setText(R.string.connecting_bluetooth);
innerLayout.addView(connecting);
layout.addView(innerLayout);
manager.startBluetoothConnectionWorker(this);
}
setContentView(layout);
manager.tryToConnect(this);
}
public void connectionEstablished() {
final Intent intent = new Intent(this, ConfirmationCodeActivity.class);
intent.putExtras(getIntent().getExtras());
runOnUiThread(new Runnable() {
public void run() {
startActivity(intent);
finish();
}
});
}
public void connectionNotEstablished() {
final Intent intent = new Intent(this, ConnectionFailedActivity.class);
runOnUiThread(new Runnable() {
public void run() {
startActivity(intent);
finish();
}
});
}
}

View File

@@ -1,109 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ConnectionFailedActivity extends Activity
implements WifiStateListener, BluetoothStateListener, OnClickListener {
private WifiWidget wifi = null;
private BluetoothWidget bluetooth = null;
private Button tryAgainButton = null;
private String networkName = null;
private boolean useBluetooth = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(this);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.alerts_and_states_error);
innerLayout.addView(icon);
TextView failed = new TextView(this);
failed.setTextSize(20);
failed.setText(R.string.connection_failed);
innerLayout.addView(failed);
layout.addView(innerLayout);
TextView checkNetwork = new TextView(this);
checkNetwork.setGravity(CENTER_HORIZONTAL);
checkNetwork.setText(R.string.check_same_network);
layout.addView(checkNetwork);
wifi = new WifiWidget(this);
wifi.init(this);
layout.addView(wifi);
bluetooth = new BluetoothWidget(this);
bluetooth.init(this);
layout.addView(bluetooth);
tryAgainButton = new Button(this);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
tryAgainButton.setLayoutParams(lp);
tryAgainButton.setText(R.string.try_again_button);
tryAgainButton.setOnClickListener(this);
enabledOrDisableTryAgainButton();
layout.addView(tryAgainButton);
setContentView(layout);
}
@Override
public void onResume() {
super.onResume();
wifi.populate();
bluetooth.populate();
}
public void wifiStateChanged(String networkName) {
this.networkName = networkName;
enabledOrDisableTryAgainButton();
}
public void bluetoothStateChanged(boolean enabled) {
useBluetooth = enabled;
enabledOrDisableTryAgainButton();
}
private void enabledOrDisableTryAgainButton() {
if(tryAgainButton == null) return; // Activity not created yet
if(useBluetooth || networkName != null) tryAgainButton.setEnabled(true);
else tryAgainButton.setEnabled(false);
}
public void onClick(View view) {
Intent intent = new Intent(this, InvitationCodeActivity.class);
intent.putExtra("net.sf.briar.android.invitation.NETWORK_NAME",
networkName);
intent.putExtra("net.sf.briar.android.invitation.USE_BLUETOOTH",
useBluetooth);
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,86 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import net.sf.briar.R;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ConnectionFailedView extends AddContactView
implements WifiStateListener, BluetoothStateListener, OnClickListener {
private Button tryAgainButton = null;
ConnectionFailedView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(ctx);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.alerts_and_states_error);
innerLayout.addView(icon);
TextView failed = new TextView(ctx);
failed.setTextSize(20);
failed.setText(R.string.connection_failed);
innerLayout.addView(failed);
addView(innerLayout);
TextView checkNetwork = new TextView(ctx);
checkNetwork.setGravity(CENTER_HORIZONTAL);
checkNetwork.setText(R.string.check_same_network);
addView(checkNetwork);
WifiWidget wifi = new WifiWidget(ctx);
wifi.init(this);
addView(wifi);
BluetoothWidget bluetooth = new BluetoothWidget(ctx);
bluetooth.init(this);
addView(bluetooth);
tryAgainButton = new Button(ctx);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
tryAgainButton.setLayoutParams(lp);
tryAgainButton.setText(R.string.try_again_button);
tryAgainButton.setOnClickListener(this);
enabledOrDisableTryAgainButton();
addView(tryAgainButton);
}
public void wifiStateChanged(String networkName) {
container.setNetworkName(networkName);
enabledOrDisableTryAgainButton();
}
public void bluetoothStateChanged(boolean enabled) {
container.setUseBluetooth(enabled);
enabledOrDisableTryAgainButton();
}
private void enabledOrDisableTryAgainButton() {
if(tryAgainButton == null) return; // Activity not created yet
boolean useBluetooth = container.getUseBluetooth();
String networkName = container.getNetworkName();
if(useBluetooth || networkName != null) tryAgainButton.setEnabled(true);
else tryAgainButton.setEnabled(false);
}
public void onClick(View view) {
// Try again
container.setView(new InvitationCodeView(container));
}
}

View File

@@ -1,8 +0,0 @@
package net.sf.briar.android.invitation;
interface ConnectionListener {
void connectionEstablished();
void connectionNotEstablished();
}

View File

@@ -0,0 +1,71 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import android.content.Context;
import android.content.res.Resources;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
public class ConnectionView extends AddContactView {
ConnectionView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
TextView yourCode = new TextView(ctx);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_invitation_code);
addView(yourCode);
TextView code = new TextView(ctx);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
int localCode = container.getLocalInvitationCode();
code.setText(String.format("%06d", localCode));
addView(code);
String networkName = container.getNetworkName();
if(networkName != null) {
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(ctx);
progress.setIndeterminate(true);
progress.setPadding(0, 10, 10, 0);
innerLayout.addView(progress);
TextView connecting = new TextView(ctx);
Resources res = getResources();
String connectingVia = res.getString(R.string.connecting_wifi);
connecting.setText(String.format(connectingVia, networkName));
innerLayout.addView(connecting);
addView(innerLayout);
}
boolean useBluetooth = container.getUseBluetooth();
if(useBluetooth) {
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(ctx);
progress.setPadding(0, 10, 10, 0);
progress.setIndeterminate(true);
innerLayout.addView(progress);
TextView connecting = new TextView(ctx);
connecting.setText(R.string.connecting_bluetooth);
innerLayout.addView(connecting);
addView(innerLayout);
}
}
}

View File

@@ -2,17 +2,11 @@ package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.content.Context;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
@@ -20,43 +14,42 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
public class ContactAddedActivity extends Activity implements OnClickListener,
public class ContactAddedView extends AddContactView implements OnClickListener,
OnEditorActionListener {
private volatile Button done = null;
private Button done = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
ContactAddedView(Context ctx) {
super(ctx);
}
LinearLayout innerLayout = new LinearLayout(this);
void populate() {
removeAllViews();
Context ctx = getContext();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(this);
ImageView icon = new ImageView(ctx);
icon.setImageResource(R.drawable.navigation_accept);
icon.setPadding(10, 10, 10, 10);
innerLayout.addView(icon);
TextView failed = new TextView(this);
TextView failed = new TextView(ctx);
failed.setText(R.string.contact_added);
failed.setTextSize(20);
innerLayout.addView(failed);
layout.addView(innerLayout);
addView(innerLayout);
TextView enterNickname = new TextView(this);
TextView enterNickname = new TextView(ctx);
enterNickname.setGravity(CENTER_HORIZONTAL);
enterNickname.setText(R.string.enter_nickname);
layout.addView(enterNickname);
addView(enterNickname);
final Button addAnother = new Button(this);
final Button done = new Button(this);
final Button addAnother = new Button(ctx);
final Button done = new Button(ctx);
this.done = done;
EditText nicknameEntry = new EditText(this) {
EditText nicknameEntry = new EditText(ctx) {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
@@ -68,9 +61,9 @@ OnEditorActionListener {
nicknameEntry.setMaxEms(20);
nicknameEntry.setMaxLines(1);
nicknameEntry.setOnEditorActionListener(this);
layout.addView(nicknameEntry);
addView(nicknameEntry);
innerLayout = new LinearLayout(this);
innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
@@ -83,20 +76,16 @@ OnEditorActionListener {
done.setEnabled(false);
done.setOnClickListener(this);
innerLayout.addView(done);
layout.addView(innerLayout);
setContentView(layout);
addView(innerLayout);
}
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
if(textView.getText().length() > 0) finish();
if(textView.getText().length() > 0) container.finish();
return true;
}
public void onClick(View view) {
if(done == null) return;
if(view != done)
startActivity(new Intent(this, NetworkSetupActivity.class));
finish();
if(view == done) container.finish(); // Done
else container.setView(new NetworkSetupView(container)); // Add another
}
}

View File

@@ -1,56 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import roboguice.activity.RoboActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.inject.Inject;
public class InvitationCodeActivity extends RoboActivity
implements CodeEntryListener {
@Inject private InvitationManager manager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
TextView yourCode = new TextView(this);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_invitation_code);
layout.addView(yourCode);
TextView code = new TextView(this);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
code.setText(manager.getLocalInvitationCode());
layout.addView(code);
CodeEntryWidget codeEntry = new CodeEntryWidget(this);
Resources res = getResources();
codeEntry.init(this, res.getString(R.string.enter_invitation_code));
layout.addView(codeEntry);
setContentView(layout);
}
public void codeEntered(String code) {
manager.setRemoteInvitationCode(code);
Intent intent = new Intent(this, ConnectionActivity.class);
intent.putExtras(getIntent().getExtras());
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,46 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import android.content.Context;
import android.content.res.Resources;
import android.widget.TextView;
public class InvitationCodeView extends AddContactView
implements CodeEntryListener {
private int localCode = -1;
InvitationCodeView(Context ctx) {
super(ctx);
}
void init(AddContactActivity container) {
localCode = container.generateLocalInvitationCode();
super.init(container);
}
void populate() {
removeAllViews();
Context ctx = getContext();
TextView yourCode = new TextView(ctx);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_invitation_code);
addView(yourCode);
TextView code = new TextView(ctx);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
code.setText(String.format("%06d", localCode));
addView(code);
CodeEntryWidget codeEntry = new CodeEntryWidget(ctx);
Resources res = getResources();
codeEntry.init(this, res.getString(R.string.enter_invitation_code));
addView(codeEntry);
}
public void codeEntered(int remoteCode) {
container.remoteInvitationCodeEntered(remoteCode);
}
}

View File

@@ -1,26 +0,0 @@
package net.sf.briar.android.invitation;
import android.content.Context;
interface InvitationManager {
int TIMEOUT = 20 * 1000;
void tryToConnect(ConnectionListener listener);
String getLocalInvitationCode();
String getRemoteInvitationCode();
void setRemoteInvitationCode(String code);
void startWifiConnectionWorker(Context ctx);
void startBluetoothConnectionWorker(Context ctx);
String getLocalConfirmationCode();
String getRemoteConfirmationCode();
void startConfirmationWorker(ConfirmationListener listener);
}

View File

@@ -1,67 +0,0 @@
package net.sf.briar.android.invitation;
import android.content.Context;
import android.util.Log;
class InvitationManagerImpl implements InvitationManager {
public void tryToConnect(final ConnectionListener listener) {
new Thread() {
@Override
public void run() {
try {
// FIXME
Thread.sleep((long) (Math.random() * TIMEOUT));
if(Math.random() < 0.5) listener.connectionEstablished();
else listener.connectionNotEstablished();
} catch(InterruptedException e) {
Log.w(getClass().getName(), e.toString());
listener.connectionNotEstablished();
}
}
}.start();
}
public String getLocalInvitationCode() {
// FIXME
return "123456";
}
public String getRemoteInvitationCode() {
// FIXME
return "123456";
}
public void setRemoteInvitationCode(String code) {
// FIXME
}
public void startWifiConnectionWorker(Context ctx) {
// FIXME
}
public void startBluetoothConnectionWorker(Context ctx) {
// FIXME
}
public String getLocalConfirmationCode() {
// FIXME
return "123456";
}
public String getRemoteConfirmationCode() {
// FIXME
return "123456";
}
public void startConfirmationWorker(ConfirmationListener listener) {
// FIXME
try {
Thread.sleep(1000 + (int) (Math.random() * 4 * 1000));
} catch(InterruptedException e) {
Log.w(getClass().getName(), e.toString());
Thread.currentThread().interrupt();
}
listener.confirmationReceived();
}
}

View File

@@ -1,99 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class NetworkSetupActivity extends Activity
implements WifiStateListener, BluetoothStateListener, OnClickListener {
private WifiWidget wifi = null;
private BluetoothWidget bluetooth = null;
private Button continueButton = null;
private String networkName = null;
private boolean useBluetooth = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
TextView sameNetwork = new TextView(this);
sameNetwork.setGravity(CENTER_HORIZONTAL);
sameNetwork.setText(R.string.same_network);
layout.addView(sameNetwork);
wifi = new WifiWidget(this);
wifi.init(this);
layout.addView(wifi);
bluetooth = new BluetoothWidget(this);
bluetooth.init(this);
layout.addView(bluetooth);
continueButton = new Button(this);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
continueButton.setLayoutParams(lp);
continueButton.setText(R.string.continue_button);
continueButton.setOnClickListener(this);
enableOrDisableContinueButton();
layout.addView(continueButton);
setContentView(layout);
}
@Override
public void onResume() {
super.onResume();
wifi.populate();
bluetooth.populate();
}
public void wifiStateChanged(final String name) {
runOnUiThread(new Runnable() {
public void run() {
networkName = name;
enableOrDisableContinueButton();
}
});
}
public void bluetoothStateChanged(final boolean enabled) {
runOnUiThread(new Runnable() {
public void run() {
useBluetooth = enabled;
enableOrDisableContinueButton();
}
});
}
private void enableOrDisableContinueButton() {
if(continueButton == null) return; // Activity not created yet
if(useBluetooth || networkName != null) continueButton.setEnabled(true);
else continueButton.setEnabled(false);
}
public void onClick(View view) {
Intent intent = new Intent(this, InvitationCodeActivity.class);
intent.putExtra("net.sf.briar.android.invitation.NETWORK_NAME",
networkName);
intent.putExtra("net.sf.briar.android.invitation.USE_BLUETOOTH",
useBluetooth);
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,76 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import net.sf.briar.R;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class NetworkSetupView extends AddContactView
implements WifiStateListener, BluetoothStateListener, OnClickListener {
private Button continueButton = null;
NetworkSetupView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
TextView sameNetwork = new TextView(ctx);
sameNetwork.setGravity(CENTER_HORIZONTAL);
sameNetwork.setText(R.string.same_network);
addView(sameNetwork);
WifiWidget wifi = new WifiWidget(ctx);
wifi.init(this);
addView(wifi);
BluetoothWidget bluetooth = new BluetoothWidget(ctx);
bluetooth.init(this);
addView(bluetooth);
continueButton = new Button(ctx);
LayoutParams lp = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
continueButton.setLayoutParams(lp);
continueButton.setText(R.string.continue_button);
continueButton.setOnClickListener(this);
enableOrDisableContinueButton();
addView(continueButton);
}
public void wifiStateChanged(final String networkName) {
container.runOnUiThread(new Runnable() {
public void run() {
container.setNetworkName(networkName);
enableOrDisableContinueButton();
}
});
}
public void bluetoothStateChanged(final boolean enabled) {
container.runOnUiThread(new Runnable() {
public void run() {
container.setUseBluetooth(enabled);
enableOrDisableContinueButton();
}
});
}
private void enableOrDisableContinueButton() {
if(continueButton == null) return; // Activity not created yet
boolean useBluetooth = container.getUseBluetooth();
String networkName = container.getNetworkName();
if(useBluetooth || networkName != null) continueButton.setEnabled(true);
else continueButton.setEnabled(false);
}
public void onClick(View view) {
// Continue
container.setView(new InvitationCodeView(container));
}
}

View File

@@ -1,89 +0,0 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import net.sf.briar.R;
import roboguice.activity.RoboActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.inject.Inject;
public class WaitForContactActivity extends RoboActivity
implements ConfirmationListener {
@Inject private InvitationManager manager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
LinearLayout innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(this);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.navigation_accept);
innerLayout.addView(icon);
TextView failed = new TextView(this);
failed.setTextSize(20);
failed.setText(R.string.connected_to_contact);
innerLayout.addView(failed);
layout.addView(innerLayout);
TextView yourCode = new TextView(this);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_confirmation_code);
layout.addView(yourCode);
TextView code = new TextView(this);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
code.setText(manager.getLocalConfirmationCode());
layout.addView(code);
innerLayout = new LinearLayout(this);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(this);
progress.setIndeterminate(true);
progress.setPadding(0, 10, 10, 0);
innerLayout.addView(progress);
TextView connecting = new TextView(this);
connecting.setText(R.string.waiting_for_contact);
innerLayout.addView(connecting);
layout.addView(innerLayout);
setContentView(layout);
manager.startConfirmationWorker(this);
}
public void confirmationReceived() {
startActivity(new Intent(this, ContactAddedActivity.class));
finish();
}
public void confirmationNotReceived() {
Intent intent = new Intent(this, CodesDoNotMatchActivity.class);
intent.putExtras(getIntent().getExtras());
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,62 @@
package net.sf.briar.android.invitation;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import android.content.Context;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
public class WaitForContactView extends AddContactView {
WaitForContactView(Context ctx) {
super(ctx);
}
void populate() {
removeAllViews();
Context ctx = getContext();
LinearLayout innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ImageView icon = new ImageView(ctx);
icon.setPadding(10, 10, 10, 10);
icon.setImageResource(R.drawable.navigation_accept);
innerLayout.addView(icon);
TextView failed = new TextView(ctx);
failed.setTextSize(20);
failed.setText(R.string.connected_to_contact);
innerLayout.addView(failed);
addView(innerLayout);
TextView yourCode = new TextView(ctx);
yourCode.setGravity(CENTER_HORIZONTAL);
yourCode.setText(R.string.your_confirmation_code);
addView(yourCode);
TextView code = new TextView(ctx);
code.setGravity(CENTER_HORIZONTAL);
code.setTextSize(50);
int localCode = container.getLocalConfirmationCode();
code.setText(String.format("%06d", localCode));
addView(code);
innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
ProgressBar progress = new ProgressBar(ctx);
progress.setIndeterminate(true);
progress.setPadding(0, 10, 10, 0);
innerLayout.addView(progress);
TextView connecting = new TextView(ctx);
connecting.setText(R.string.waiting_for_contact);
innerLayout.addView(connecting);
addView(innerLayout);
}
}

View File

@@ -11,7 +11,7 @@ public interface CryptoComponent {
/**
* Derives a tag key from the given temporary secret.
* @param alice Indicates whether the key is for connections initiated by
* @param alice indicates whether the key is for connections initiated by
* Alice or Bob.
*/
ErasableKey deriveTagKey(byte[] secret, boolean alice);
@@ -19,9 +19,9 @@ public interface CryptoComponent {
/**
* Derives a frame key from the given temporary secret and connection
* number.
* @param alice Indicates whether the key is for a connection initiated by
* @param alice indicates whether the key is for a connection initiated by
* Alice or Bob.
* @param initiator Indicates whether the key is for the initiator's or the
* @param initiator indicates whether the key is for the initiator's or the
* responder's side of the connection.
*/
ErasableKey deriveFrameKey(byte[] secret, long connection, boolean alice,
@@ -30,7 +30,7 @@ public interface CryptoComponent {
/**
* Derives an initial shared secret from two public keys and one of the
* corresponding private keys.
* @param alice Indicates whether the private key belongs to Alice or Bob.
* @param alice indicates whether the private key belongs to Alice or Bob.
*/
byte[] deriveInitialSecret(byte[] ourPublicKey, byte[] theirPublicKey,
PrivateKey ourPrivateKey, boolean alice);
@@ -67,7 +67,7 @@ public interface CryptoComponent {
MessageDigest getMessageDigest();
PseudoRandom getPseudoRandom(int seed);
PseudoRandom getPseudoRandom(int seed1, int seed2);
SecureRandom getSecureRandom();

View File

@@ -34,7 +34,7 @@ public interface DatabaseComponent {
/**
* Opens the database.
* @param resume True to reopen an existing database or false to create a
* @param resume true to reopen an existing database or false to create a
* new one.
*/
void open(boolean resume) throws DbException, IOException;

View File

@@ -0,0 +1,14 @@
package net.sf.briar.api.invitation;
/** An interface for informing a peer of whether confirmation codes match. */
public interface ConfirmationCallback {
/** Called to indicate that the confirmation codes match. */
void codesMatch();
/**
* Called to indicate that either the confirmation codes do not match or
* the result of the comparison is unknown.
*/
void codesDoNotMatch();
}

View File

@@ -0,0 +1,18 @@
package net.sf.briar.api.invitation;
/** An interface for monitoring the status of an invitation connection. */
public interface ConnectionCallback extends ConfirmationCallback {
/**
* Called if the connection is successfully established.
* @param localCode the local confirmation code.
* @param remoteCode the remote confirmation code.
* @param c a callback to inform the remote peer of the result of the local
* peer's confirmation code comparison.
*/
void connectionEstablished(int localCode, int remoteCode,
ConfirmationCallback c);
/** Called if the connection cannot be established. */
void connectionNotEstablished();
}

View File

@@ -0,0 +1,17 @@
package net.sf.briar.api.invitation;
/**
* Allows invitation connections to be established and their status to be
* monitored.
*/
public interface InvitationManager {
/**
* Tries to establish an invitation connection.
* @param localCode the local invitation code.
* @param remoteCode the remote invitation code.
* @param c1 a callback to be informed of the connection's status and the
* result of the remote peer's confirmation code comparison.
*/
void connect(int localCode, int remoteCode, ConnectionCallback c);
}

View File

@@ -6,9 +6,9 @@ public interface InvitationConstants {
int CODE_BITS = 19; // Codes must fit into six decimal digits
int MAX_CODE = 1 << CODE_BITS - 1;
int MAX_CODE = (1 << CODE_BITS) - 1;
int HASH_LENGTH = 48;
int HASH_LENGTH = 48; // Bytes
int MAX_PUBLIC_KEY_LENGTH = 120;
int MAX_PUBLIC_KEY_LENGTH = 120; // Bytes
}

View File

@@ -21,14 +21,14 @@ public interface Plugin {
void stop() throws IOException;
/**
* Returns true if the plugin's poll() method should be called
* periodically to attempt to establish connections.
* Returns true if the plugin's {@link Plugin#poll(Collection)} method
* should be called periodically to attempt to establish connections.
*/
boolean shouldPoll();
/**
* Returns the desired interval in milliseconds between calls to the
* plugin's poll() method.
* plugin's {@link Plugin#poll(Collection)} method.
*/
long getPollingInterval();

View File

@@ -33,7 +33,7 @@ public interface PluginCallback {
* Presents the user with a choice among two or more named options and
* returns the user's response. The message may consist of a translatable
* format string and arguments.
* @return An index into the array of options indicating the user's choice,
* @return an index into the array of options indicating the user's choice,
* or -1 if the user cancelled the choice.
*/
int showChoice(String[] options, String... message);

View File

@@ -5,6 +5,10 @@ import java.util.Collection;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import android.content.Context;
/**
* Responsible for starting transport plugins at startup, stopping them at
* shutdown, and providing access to plugins for exchanging invitations.
*/
public interface PluginManager {
/**

View File

@@ -9,8 +9,7 @@ import java.io.InputStream;
*/
public interface SimplexTransportReader {
/** Returns an input stream for reading from the transport.
* @throws IOException */
/** Returns an input stream for reading from the transport. */
InputStream getInputStream() throws IOException;
/**

View File

@@ -12,8 +12,7 @@ public interface SimplexTransportWriter {
/** Returns the capacity of the transport in bytes. */
long getCapacity();
/** Returns an output stream for writing to the transport.
* @throws IOException */
/** Returns an output stream for writing to the transport. */
OutputStream getOutputStream() throws IOException;
/**

View File

@@ -6,7 +6,7 @@ public interface UiCallback {
* Presents the user with a choice among two or more named options and
* returns the user's response. The message may consist of a translatable
* format string and arguments.
* @return An index into the array of options indicating the user's choice,
* @return an index into the array of options indicating the user's choice,
* or -1 if the user cancelled the choice.
*/
int showChoice(String[] options, String... message);

View File

@@ -274,8 +274,8 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public PseudoRandom getPseudoRandom(int seed) {
return new PseudoRandomImpl(getMessageDigest(), seed);
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
return new PseudoRandomImpl(getMessageDigest(), seed1, seed2);
}
public SecureRandom getSecureRandom() {

View File

@@ -11,10 +11,11 @@ class PseudoRandomImpl implements PseudoRandom {
private byte[] state;
private int offset;
PseudoRandomImpl(MessageDigest messageDigest, int seed) {
PseudoRandomImpl(MessageDigest messageDigest, int seed1, int seed2) {
this.messageDigest = messageDigest;
byte[] seedBytes = new byte[4];
ByteUtils.writeUint32(seed, seedBytes, 0);
byte[] seedBytes = new byte[8];
ByteUtils.writeUint32(seed1, seedBytes, 0);
ByteUtils.writeUint32(seed2, seedBytes, 4);
messageDigest.update(seedBytes);
state = messageDigest.digest();
offset = 0;

View File

@@ -24,9 +24,9 @@ import net.sf.briar.api.transport.TemporarySecret;
/**
* A low-level interface to the database (DatabaseComponent provides a
* high-level interface). Most operations take a transaction argument, which is
* obtained by calling startTransaction(). Every transaction must be
* terminated by calling either abortTransaction() or commitTransaction(),
* even if an exception is thrown.
* obtained by calling {@link #startTransaction()}. Every transaction must be
* terminated by calling either {@link #abortTransaction(T)} or
* {@link #commitTransaction(T)}, even if an exception is thrown.
* <p>
* Locking is provided by the DatabaseComponent implementation. To prevent
* deadlock, locks must be acquired in the following order:
@@ -45,7 +45,7 @@ interface Database<T> {
/**
* Opens the database.
* @param resume True to reopen an existing database, false to create a
* @param resume true to reopen an existing database, false to create a
* new one.
*/
void open(boolean resume) throws DbException, IOException;

View File

@@ -326,7 +326,7 @@ DatabaseCleaner.Callback {
* that have changed from sendable to not sendable, or vice versa.
* <p>
* Locking: message write.
* @param increment True if the message's sendability has changed from 0 to
* @param increment true if the message's sendability has changed from 0 to
* greater than 0, or false if it has changed from greater than 0 to 0.
*/
private int updateAncestorSendability(T txn, MessageId m, boolean increment)
@@ -1396,7 +1396,7 @@ DatabaseCleaner.Callback {
* the ancestors of those messages if necessary.
* <p>
* Locking: message write.
* @param increment True if the user's rating for the author has changed
* @param increment true if the user's rating for the author has changed
* from not good to good, or false if it has changed from good to not good.
*/
private void updateAuthorSendability(T txn, AuthorId a, boolean increment)

View File

@@ -0,0 +1,81 @@
package net.sf.briar.invitation;
import java.util.Collection;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.invitation.ConfirmationCallback;
import net.sf.briar.api.invitation.ConnectionCallback;
import net.sf.briar.api.invitation.InvitationManager;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import com.google.inject.Inject;
class InvitationManagerImpl implements InvitationManager {
private final CryptoComponent crypto;
private final PluginManager pluginManager;
@Inject
InvitationManagerImpl(CryptoComponent crypto, PluginManager pluginManager) {
this.crypto = crypto;
this.pluginManager = pluginManager;
}
public void connect(int localCode, int remoteCode, ConnectionCallback c) {
Collection<DuplexPlugin> plugins = pluginManager.getInvitationPlugins();
// Alice is the party with the smaller invitation code
if(localCode < remoteCode) {
PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode);
startAliceInvitationWorker(plugins, r, c);
} else {
PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode);
startBobInvitationWorker(plugins, r, c);
}
}
private void startAliceInvitationWorker(Collection<DuplexPlugin> plugins,
PseudoRandom r, ConnectionCallback c) {
// FIXME
new FakeWorkerThread(c).start();
}
private void startBobInvitationWorker(Collection<DuplexPlugin> plugins,
PseudoRandom r, ConnectionCallback c) {
// FIXME
new FakeWorkerThread(c).start();
}
private static class FakeWorkerThread extends Thread {
private final ConnectionCallback callback;
private FakeWorkerThread(ConnectionCallback callback) {
this.callback = callback;
}
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 30 * 1000));
} catch(InterruptedException ignored) {}
if(Math.random() < 0.8) {
callback.connectionNotEstablished();
} else {
callback.connectionEstablished(123456, 123456,
new ConfirmationCallback() {
public void codesMatch() {}
public void codesDoNotMatch() {}
});
try {
Thread.sleep((long) (Math.random() * 10 * 1000));
} catch(InterruptedException ignored) {}
if(Math.random() < 0.5) callback.codesMatch();
else callback.codesDoNotMatch();
}
}
}
}

View File

@@ -0,0 +1,13 @@
package net.sf.briar.invitation;
import net.sf.briar.api.invitation.InvitationManager;
import com.google.inject.AbstractModule;
public class InvitationModule extends AbstractModule {
@Override
protected void configure() {
bind(InvitationManager.class).to(InvitationManagerImpl.class);
}
}

View File

@@ -95,6 +95,7 @@ class PluginManagerImpl implements PluginManager {
public synchronized int start(Context appContext) {
Set<TransportId> ids = new HashSet<TransportId>();
// Instantiate and start the simplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Starting simplex plugins");
for(String s : getSimplexPluginFactoryNames()) {
try {
Class<?> c = Class.forName(s);
@@ -128,6 +129,7 @@ class PluginManagerImpl implements PluginManager {
}
}
// Instantiate and start the duplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Starting duplex plugins");
for(String s : getDuplexPluginFactoryNames()) {
try {
Class<?> c = Class.forName(s);
@@ -161,6 +163,7 @@ class PluginManagerImpl implements PluginManager {
}
}
// Start the poller
if(LOG.isLoggable(INFO)) LOG.info("Starting poller");
List<Plugin> plugins = new ArrayList<Plugin>();
plugins.addAll(simplexPlugins);
plugins.addAll(duplexPlugins);
@@ -182,8 +185,10 @@ class PluginManagerImpl implements PluginManager {
public synchronized int stop() {
int stopped = 0;
// Stop the poller
if(LOG.isLoggable(INFO)) LOG.info("Stopping poller");
poller.stop();
// Stop the simplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Stopping simplex plugins");
for(SimplexPlugin plugin : simplexPlugins) {
try {
plugin.stop();
@@ -192,7 +197,9 @@ class PluginManagerImpl implements PluginManager {
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
}
}
simplexPlugins.clear();
// Stop the duplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Stopping duplex plugins");
for(DuplexPlugin plugin : duplexPlugins) {
try {
plugin.stop();
@@ -201,7 +208,9 @@ class PluginManagerImpl implements PluginManager {
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
}
}
duplexPlugins.clear();
// Shut down the executors
if(LOG.isLoggable(INFO)) LOG.info("Stopping executors");
pluginExecutor.shutdown();
androidExecutor.shutdown();
// Return the number of plugins successfully stopped

View File

@@ -349,10 +349,10 @@ class DroidtoothPlugin implements DuplexPlugin {
if(!running) return;
}
if(adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) return;
Intent intent = new Intent(ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(EXTRA_DISCOVERABLE_DURATION, 60);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(intent);
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 120);
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(i);
}
private static class BluetoothStateReceiver extends BroadcastReceiver {

View File

@@ -11,7 +11,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
/**
* A ConnectionWriter that buffers its input and writes a frame whenever there
* is a full frame to write or the flush() method is called.
* is a full frame to write or the {@link #flush()} method is called.
* <p>
* This class is not thread-safe.
*/