Merge branch 'master' into ble
diff --git a/lib/build.gradle b/lib/build.gradle
index 602e1b3..2ed0b9a 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -105,6 +105,7 @@
}
tasks.'processResources'.dependsOn(generateVdl)
+tasks.'compileJava'.dependsOn(generateVdl)
task checkVanadiumEnvironment {
VanadiumEnvironment.getVanadiumEnvironment()
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
index 4dd4dea..2790330 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
@@ -13,6 +13,10 @@
import android.support.v4.widget.DrawerLayout;
import io.v.android.apps.syncslides.db.DB;
+import io.v.android.apps.syncslides.discovery.V23Manager;
+import io.v.android.v23.services.blessing.BlessingCreationException;
+import io.v.v23.security.Blessings;
+import io.v.v23.verror.VException;
public class DeckChooserActivity extends AppCompatActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
@@ -27,7 +31,11 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Do this initialization early on in case it needs to start the AccountManager.
+ Log.d(TAG, "onCreate");
+ // Immediately initialize V23, possibly sending user to the
+ // AccountManager to get blessings.
+ V23Manager.Singleton.get().init(getApplicationContext(), this);
+
mDB = DB.Singleton.get(getApplicationContext());
mDB.init(this);
@@ -79,10 +87,12 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (mDB.onActivityResult(requestCode, resultCode, data)) {
+ Log.d(TAG, "onActivityResult");
+ if (V23Manager.onActivityResult(
+ getApplicationContext(), requestCode, resultCode, data)) {
+ Log.d(TAG, "did the v23 result");
return;
}
// Any other activity results would be handled here.
}
-
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
index c525a9c..3555157 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
@@ -41,7 +41,8 @@
throw new IllegalStateException("Wrong lifecycle.");
}
Log.d(TAG, "Starting.");
- DiscoveryManager dm = DiscoveryManager.Singleton.get();
+ DiscoveryManager dm = DiscoveryManager.make();
+ // Listening stops below in mLiveDecks.discard.
dm.setListener(this);
dm.start(context);
mLiveDecks = dm;
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
index c55e62d..48fafc0 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
@@ -13,7 +13,8 @@
import android.widget.Toast;
import io.v.android.apps.syncslides.db.DB;
-import io.v.android.apps.syncslides.discovery.ParticipantPeer;
+import io.v.android.apps.syncslides.discovery.ParticipantServerImpl;
+import io.v.android.apps.syncslides.discovery.V23Manager;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckImpl;
import io.v.android.apps.syncslides.model.Participant;
@@ -34,15 +35,28 @@
private String mPresentationId = "randomPresentation1";
private boolean mSynced;
+ /**
+ * Once a user clicks 'present' - which happens at some unpredictable time
+ * after onStart, the user begins presenting the deck, and the system must
+ * advertise the presentation. Once advertising is started, it doesn't
+ * stop until onStop is called. If the activity is paused, advertising
+ * should continue.
+ */
+ private boolean mShouldBeAdvertising;
+ private boolean mIsAdvertising;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
- // Do this initialization early on in case it needs to start the AccountManager.
+ // Immediately initialize V23, possibly sending user to the
+ // AccountManager to get blessings.
+ V23Manager.Singleton.get().init(getApplicationContext(), this);
DB.Singleton.get(getApplicationContext()).init(this);
-
setContentView(R.layout.activity_presentation);
+ mShouldBeAdvertising = false;
+ mIsAdvertising = false;
if (savedInstanceState == null) {
Log.d(TAG, "savedInstanceState is null");
mDeck = DeckImpl.fromBundle(getIntent().getExtras());
@@ -54,6 +68,10 @@
mDeck = DeckImpl.fromBundle(savedInstanceState);
mRole = (Role) savedInstanceState.get(Participant.B.PARTICIPANT_ROLE);
mSynced = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SYNCED);
+ mShouldBeAdvertising = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SHOULD_ADV);
+ if (mShouldBeAdvertising) {
+ Log.d(TAG, "Need to restore advertising");
+ }
}
// TODO(jregan): This appears to be an attempt to avoid fragment
@@ -63,6 +81,10 @@
return;
}
+ if (mShouldBeAdvertising){
+ startAdvertising();
+ }
+
getSupportActionBar().setTitle(mDeck.getTitle());
// If this is an audience member, we want them to jump straight to the fullscreen view.
@@ -74,32 +96,41 @@
}
@Override
- protected void onSaveInstanceState(Bundle b) {
- Log.d(TAG, "onSaveInstanceState1");
- super.onSaveInstanceState(b);
- Log.d(TAG, "onSaveInstanceState2");
- packBundle(b);
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ Log.d(TAG, "onActivityResult");
+ if (V23Manager.onActivityResult(
+ getApplicationContext(), requestCode, resultCode, data)) {
+ Log.d(TAG, "did the v23 result");
+ return;
+ }
+ // Any other activity results would be handled here.
}
- private Bundle packBundle(Bundle b) {
+ @Override
+ protected void onSaveInstanceState(Bundle b) {
+ Log.d(TAG, "onSaveInstanceState");
+ super.onSaveInstanceState(b);
mDeck.toBundle(b);
b.putSerializable(Participant.B.PARTICIPANT_ROLE, mRole);
b.putBoolean(Participant.B.PARTICIPANT_SYNCED, mSynced);
- return b;
+ b.putBoolean(Participant.B.PARTICIPANT_SHOULD_ADV, mShouldBeAdvertising);
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
+ if (mShouldBeAdvertising){
+ startAdvertising();
+ }
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
- // Don't shutdown v23 at this point.
- // TODO(jregan): Stop advertising the live presentation if necessary.
+ stopAdvertising();
}
/**
@@ -125,15 +156,43 @@
}
}
- /**
- * Start the service for advertising a presentation.
- */
- private void beginAdvertising() {
- Log.d(TAG, "beginAdvertising");
- Intent intent = new Intent(this, ParticipantPeer.class);
- intent.putExtras(packBundle(new Bundle()));
- stopService(intent);
- startService(intent);
+ private boolean shouldUseV23() {
+ return Participant.ENABLE_MT_DISCOVERY && V23Manager.Singleton.get().isBlessed();
+ }
+
+ private void startAdvertising() {
+ Log.d(TAG, "startAdvertising");
+ mShouldBeAdvertising = true;
+ if (mIsAdvertising) {
+ Log.d(TAG, "Already advertising.");
+ return;
+ }
+ if (shouldUseV23()) {
+ V23Manager.Singleton.get().mount(
+ Participant.Mt.makeMountName(mDeck),
+ new ParticipantServerImpl(mDeck));
+ Log.d(TAG, "MT advertising started.");
+ } else {
+ Log.d(TAG, "No means to start advertising.");
+ }
+ mIsAdvertising = true;
+ }
+
+ private void stopAdvertising() {
+ Log.d(TAG, "stopAdvertising");
+ if (!mIsAdvertising) {
+ Log.d(TAG, "Not advertising.");
+ return;
+ }
+ if (shouldUseV23()) {
+ // At the moment, only one service can be mounted, and this call
+ // will unmount it if mounted, else do nothing.
+ V23Manager.Singleton.get().unMount();
+ Log.d(TAG, "MT advertising stopped.");
+ } else {
+ Log.d(TAG, "No advertising to stop.");
+ }
+ mIsAdvertising = false;
}
/**
@@ -148,9 +207,7 @@
Log.i(TAG, "Started presentation");
Toast.makeText(getApplicationContext(), "Started presentation",
Toast.LENGTH_SHORT).show();
- if (Participant.ENABLE_MT_DISCOVERY) {
- beginAdvertising();
- }
+ startAdvertising();
}
});
mRole = Role.PRESENTER;
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
index a60ebc6..d3c193a 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
@@ -30,7 +30,7 @@
if (result == null) {
// Switch between FakeDB and SyncbaseDB by commenting out one.
instance = result = new FakeDB(context);
- //instance = result = new SyncbaseDB(context);
+ // instance = result = new SyncbaseDB(context);
}
}
}
@@ -48,14 +48,6 @@
void init(Activity activity);
/**
- * If init() sent an intent to another Activity, the result must be forwarded
- * from our app's activity to this method.
- *
- * @return true if the requestCode matches an intent sent by this implementation.
- */
- boolean onActivityResult(int requestCode, int resultCode, Intent data);
-
- /**
* Provides a list of elements via an API that fits well with RecyclerView.Adapter.
*/
interface DBList<E> {
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
index 1c0789a..7eaddeb 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
@@ -228,12 +228,6 @@
}
@Override
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- // Nothing to do.
- return false;
- }
-
- @Override
public void askQuestion(String deckId, String presentationId,
String firstName, String lastName) {
// Nothing to do.
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
index 2913c4c..e6b487a 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
@@ -6,7 +6,6 @@
import android.app.Activity;
import android.content.Context;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
@@ -32,37 +31,27 @@
import java.util.UUID;
import io.v.android.apps.syncslides.R;
+import io.v.android.apps.syncslides.discovery.V23Manager;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckImpl;
import io.v.android.apps.syncslides.model.Listener;
import io.v.android.apps.syncslides.model.NoopList;
import io.v.android.apps.syncslides.model.Slide;
import io.v.android.apps.syncslides.model.SlideImpl;
-import io.v.android.libs.security.BlessingsManager;
import io.v.android.v23.V;
-import io.v.android.v23.services.blessing.BlessingCreationException;
-import io.v.android.v23.services.blessing.BlessingService;
import io.v.impl.google.naming.NamingUtil;
import io.v.impl.google.services.syncbase.SyncbaseServer;
import io.v.v23.context.CancelableVContext;
import io.v.v23.context.VContext;
-import io.v.v23.namespace.Namespace;
-import io.v.v23.naming.Endpoint;
-import io.v.v23.naming.GlobReply;
-import io.v.v23.naming.MountEntry;
-import io.v.v23.naming.MountedServer;
import io.v.v23.rpc.Server;
import io.v.v23.security.BlessingPattern;
-import io.v.v23.security.Blessings;
-import io.v.v23.security.VPrincipal;
-import io.v.v23.security.VSecurity;
import io.v.v23.security.access.AccessList;
import io.v.v23.security.access.Constants;
import io.v.v23.security.access.Permissions;
-import io.v.v23.services.watch.ResumeMarker;
import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
import io.v.v23.services.syncbase.nosql.SyncgroupPrefix;
import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
+import io.v.v23.services.watch.ResumeMarker;
import io.v.v23.syncbase.Syncbase;
import io.v.v23.syncbase.SyncbaseApp;
import io.v.v23.syncbase.SyncbaseService;
@@ -70,7 +59,6 @@
import io.v.v23.syncbase.nosql.ChangeType;
import io.v.v23.syncbase.nosql.Database;
import io.v.v23.syncbase.nosql.DatabaseCore;
-import io.v.v23.syncbase.nosql.PrefixRange;
import io.v.v23.syncbase.nosql.RowRange;
import io.v.v23.syncbase.nosql.Stream;
import io.v.v23.syncbase.nosql.Syncgroup;
@@ -84,11 +72,6 @@
public class SyncbaseDB implements DB {
private static final String TAG = "SyncbaseDB";
- /**
- * The intent result code for when we get blessings from the account manager.
- * The value must not conflict with any other blessing result codes.
- */
- private static final int BLESSING_REQUEST = 200;
private static final String SYNCBASE_APP = "syncslides";
private static final String SYNCBASE_DB = "syncslides";
private static final String DECKS_TABLE = "Decks";
@@ -97,13 +80,7 @@
static final String CURRENT_SLIDE = "CurrentSlide";
static final String QUESTIONS = "questions";
private static final String SYNCGROUP_PRESENTATION_DESCRIPTION = "Live Presentation";
- private static final String PI_MILK_CRATE = "192.168.86.254:8101";
- // If SyncbaseDB needs to start the AccountManager to get blessings, it will not
- // finish its initialization, but the fragment that is trying to initialize
- // DB will continue to load and use DB. That fragment will reload when the
- // AccountManager is finished, so if mInitialized is false, any DB methods should
- // return noop values.
private boolean mInitialized = false;
private Handler mHandler;
private Permissions mPermissions;
@@ -125,11 +102,12 @@
@Override
public void init(Activity activity) {
+ Log.d(TAG, "init");
if (mInitialized) {
+ Log.d(TAG, "already initialized");
return;
}
mHandler = new Handler(Looper.getMainLooper());
- mVContext = V.init(mContext);
// TODO(kash): Set proper ACLs.
AccessList acl = new AccessList(
ImmutableList.of(new BlessingPattern("...")), ImmutableList.<String>of());
@@ -138,68 +116,22 @@
Constants.READ.getValue(), acl,
Constants.WRITE.getValue(), acl,
Constants.ADMIN.getValue(), acl));
- getBlessings(activity);
- }
- private void getBlessings(Activity activity) {
- Blessings blessings = null;
- try {
- // See if there are blessings stored in shared preferences.
- blessings = BlessingsManager.getBlessings(mContext);
- } catch (VException e) {
- handleError("Error getting blessings from shared preferences " + e.getMessage());
- }
- if (blessings == null) {
- // Request new blessings from the account manager via an intent. This intent
- // will call back to onActivityResult() which will continue with
- // configurePrincipal().
- refreshBlessings(activity);
+ // If blessings aren't in place, the fragment that called this
+ // initialization may continue to load and use DB, but nothing will
+ // work so DB methods should return noop values. It's assumed that
+ // the calling fragment will send the user to the AccountManager,
+ // accept blessings on return, then re-call this init.
+ if (V23Manager.Singleton.get().isBlessed()) {
+ Log.d(TAG, "no blessings.");
return;
}
- configurePrincipal(blessings);
- }
-
- private void refreshBlessings(Activity activity) {
- Intent intent = BlessingService.newBlessingIntent(mContext);
- activity.startActivityForResult(intent, BLESSING_REQUEST);
- }
-
- @Override
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == BLESSING_REQUEST) {
- try {
- byte[] blessingsVom = BlessingService.extractBlessingReply(resultCode, data);
- Blessings blessings = (Blessings) VomUtil.decode(blessingsVom, Blessings.class);
- BlessingsManager.addBlessings(mContext, blessings);
- Toast.makeText(mContext, "Success", Toast.LENGTH_SHORT).show();
- configurePrincipal(blessings);
- } catch (BlessingCreationException e) {
- handleError("Couldn't create blessing: " + e.getMessage());
- } catch (VException e) {
- handleError("Couldn't derive blessing: " + e.getMessage());
- }
- return true;
- }
- return false;
- }
-
- private void configurePrincipal(Blessings blessings) {
- // TODO(kash): Probably better to do this not in the UI thread.
- try {
- VPrincipal p = V.getPrincipal(mVContext);
- p.blessingStore().setDefaultBlessings(blessings);
- p.blessingStore().set(blessings, new BlessingPattern("..."));
- VSecurity.addToRoots(p, blessings);
- } catch (VException e) {
- handleError(String.format(
- "Couldn't set local blessing %s: %s", blessings, e.getMessage()));
- return;
- }
- setupSyncbase(blessings);
+ mVContext = V23Manager.Singleton.get().getVContext();
+ setupSyncbase();
}
// TODO(kash): Run this in an AsyncTask so it doesn't block the UI.
- private void setupSyncbase(Blessings blessings) {
+ private void setupSyncbase() {
// Prepare the syncbase storage directory.
File storageDir = new File(mContext.getFilesDir(), "syncbase");
storageDir.mkdirs();
@@ -215,7 +147,7 @@
}
mVContext = SyncbaseServer.withNewServer(mVContext, new SyncbaseServer.Params()
.withPermissions(mPermissions)
- .withName(NamingUtil.join("/", PI_MILK_CRATE, id))
+ .withName(V23Manager.syncName(id))
.withStorageRootDir(storageDir.getAbsolutePath()));
} catch (SyncbaseServer.StartException e) {
handleError("Couldn't start syncbase server");
@@ -301,7 +233,7 @@
Arrays.asList(
new SyncgroupPrefix(PRESENTATIONS_TABLE, prefix),
new SyncgroupPrefix(DECKS_TABLE, deckId)),
- Arrays.asList(NamingUtil.join("/", PI_MILK_CRATE, "sg")),
+ Arrays.asList(V23Manager.syncName("sg")),
false
),
new SyncgroupMemberInfo((byte) 10));
@@ -314,18 +246,7 @@
}
Log.i(TAG, "Finished creating syncgroup");
- Namespace namespace = V.getNamespace(mVContext);
- namespace.setRoots(Arrays.asList("/" + PI_MILK_CRATE));
- for (GlobReply reply : namespace.glob(mVContext, "...")) {
- if (reply instanceof GlobReply.Entry) {
- MountEntry entry = ((GlobReply.Entry) reply).getElem();
- Log.d(TAG, "Entry: " + entry.getName());
- for (MountedServer server : entry.getServers()) {
- String endPoint = server.getServer();
- Log.d(TAG, "Got endPoint = " + endPoint);
- }
- }
- }
+ V23Manager.Singleton.get().scan("...");
// TODO(kash): Create a syncgroup for Notes? Not sure if we should do that
// here or somewhere else. We're not going to demo sync across a user's
@@ -363,19 +284,7 @@
for (String member : syncgroup.getMembers(mVContext).keySet()) {
Log.i(TAG, "Member: " + member);
}
- Namespace namespace = V.getNamespace(mVContext);
- namespace.setRoots(Arrays.asList("/" + PI_MILK_CRATE));
- for (GlobReply reply : namespace.glob(mVContext, "...")) {
- if (reply instanceof GlobReply.Entry) {
- MountEntry entry = ((GlobReply.Entry) reply).getElem();
- Log.d(TAG, "Entry: " + entry.getName());
- for (MountedServer server : entry.getServers()) {
- String endPoint = server.getServer();
- Log.d(TAG, "Got endPoint = " + endPoint);
- }
- }
- }
-
+ V23Manager.Singleton.get().scan("...");
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
index 175c4ea..539d5b8 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
@@ -20,15 +20,13 @@
/**
* Singleton Discovery manager.
*
- * Scans a mounttable to look for presentations, and permits mounting of
- * a service representing a live presentation.
+ * Scans a mounttable to look for presentations, and permits mounting of a
+ * service representing a live presentation.
*/
public class DiscoveryManager implements DB.DBList<Deck>, Moderator.Observer {
private static final String TAG = "DiscoveryManager";
// Search result indicator.
public static final int NOT_FOUND = -1;
- // Manages creation, mounting and unmounting of V23 services.
- private final V23Manager mV23Manager;
// Scans a mount table to understand who's 'giving a presentation', hence
// the name moderator. With each scan, determines who's new
// (freshman), still there (senior) and gone (graduated).
@@ -36,11 +34,25 @@
// Runs the moderator's scan repeatedly.
private final PeriodicTasker mTasker = new PeriodicTasker();
private final List<Participant> mParticipants = new ArrayList<>();
- private Listener mListener;
private final Handler mHandler;
+ private Listener mListener;
- private DiscoveryManager(V23Manager manager, Moderator moderator) {
- mV23Manager = manager;
+ public static DiscoveryManager make() {
+ // If blessings not in place, use fake data.
+ boolean useRealDiscovery =
+ Participant.ENABLE_MT_DISCOVERY &&
+ V23Manager.Singleton.get().isBlessed();
+ if (useRealDiscovery) {
+ Log.d(TAG, "Using real discovery.");
+ return new DiscoveryManager(
+ new Moderator(new ParticipantScannerMt()));
+ }
+ Log.d(TAG, "Using fake discovery.");
+ return new DiscoveryManager(
+ new Moderator(new ParticipantScannerFake()));
+ }
+
+ private DiscoveryManager(Moderator moderator) {
mModerator = moderator;
mHandler = new Handler(Looper.getMainLooper());
}
@@ -50,16 +62,11 @@
throw new IllegalStateException("Must have a listener.");
}
Log.d(TAG, "Starting");
- if (Participant.ENABLE_MT_DISCOVERY) {
- if (mV23Manager == null) {
- throw new IllegalStateException("Must have V23.");
- }
- mV23Manager.init(context);
- }
// The observer is the guy who implements onTaskDone, and wants
// to be notified when a scan is complete.
mModerator.setObserver(this);
mTasker.start(mModerator);
+ Log.d(TAG, "Done Starting");
return this;
}
@@ -68,20 +75,6 @@
mTasker.stop();
}
- /**
- * Stops discovery and underlying V23 services.
- */
- public void stopEverything() {
- Log.d(TAG, "Stopping everything.");
- stop();
- if (Participant.ENABLE_MT_DISCOVERY) {
- if (mV23Manager == null) {
- throw new IllegalStateException("Must have V23.");
- }
- mV23Manager.shutdown(V23Manager.Behavior.STRICT);
- }
- }
-
@Override
public void onTaskDone() {
for (Participant p : mModerator.getFreshman()) {
@@ -130,35 +123,6 @@
stop();
mListener = null;
}
-
- public static class Singleton {
- private static volatile DiscoveryManager instance;
-
- public static DiscoveryManager get() {
- DiscoveryManager result = instance;
- if (instance == null) {
- synchronized (Singleton.class) {
- result = instance;
- if (result == null) {
- instance = result = makeInstance();
- }
- }
- }
- return result;
- }
-
- private static DiscoveryManager makeInstance() {
- Log.d(TAG, "Creating singleton.");
- if (Participant.ENABLE_MT_DISCOVERY) {
- V23Manager manager = V23Manager.Singleton.get();
- return new DiscoveryManager(
- manager,
- new Moderator(new ParticipantScannerMt(manager)));
- }
- return new DiscoveryManager(
- null, new Moderator(new ParticipantScannerFake()));
- }
- }
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java
deleted file mode 100644
index 1be0897..0000000
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.apps.syncslides.discovery;
-
-import java.util.List;
-
-/**
- * Generates a string for use as a V23 Service Name that won't collide with
- * an existing set.
- */
-public interface NameGenerator {
- String getName(List<String> existing, String suggested);
-}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java
deleted file mode 100644
index a44b739..0000000
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.apps.syncslides.discovery;
-
-import org.joda.time.DateTime;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
-
-import java.util.List;
-
-/**
- * Generates a string for use as a V23 Service Name based on the current time
- * down to millisecond resolution. Ugly name, but very unlikely to collide.
- */
-class NameGeneratorByDate implements NameGenerator {
- private static final DateTimeFormatter FMT =
- DateTimeFormat.forPattern("yyyy_MM_dd_hh_mm_ss_SSSS");
-
- /**
- * Ignore incoming data, just pick 'now' down to the millisecond.
- */
- @Override
- public String getName(List<String> existing, String suggested) {
- return DateTime.now().toString(FMT);
- }
-}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
index 71a06bc..9452f5b 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
@@ -4,23 +4,16 @@
package io.v.android.apps.syncslides.discovery;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.os.IBinder;
import android.util.Log;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
-import io.v.android.apps.syncslides.Role;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckImpl;
import io.v.android.apps.syncslides.model.Participant;
-import io.v.v23.context.VContext;
-import io.v.v23.rpc.ServerCall;
+import io.v.v23.verror.VException;
/**
* Someone taking part in a presentation.
@@ -37,53 +30,38 @@
* and run it as a server, using the (public) userName as part of the mount
* name.
*/
-public class ParticipantPeer extends Service implements Participant {
+public class ParticipantPeer implements Participant {
private static final String TAG = "ParticipantPeer";
private static final DateTimeFormatter TIME_FMT =
DateTimeFormat.forPattern("hh_mm_ss_SSSS");
- // V23 EndPoint of the V23 service representing the participant.
- private String mEndpointStr;
- // When did we last grab data from the endPoint? Meaningful only in
- // 'audience' mode, where the contents of mUserName etc. came from a remote
- // server rather than from being fed into the ctor.
- private DateTime mRefreshTime;
- // Name of the user participating, intended to be visible to others. This
- // can be a colloquial name as opposed to a 'real' name or email address
- // extracted from a device or blessing.
+ // V23 name of the V23 service representing the participant.
+ private String mServiceName;
+ // Visible name of human presenter.
+ // TODO(jregan): Switch to VPerson or the model equivalent.
private String mUserName;
+ // When did we last grab data from the endPoint?
+ private DateTime mRefreshTime;
// Deck the user is presenting. Can only present one at a time.
private Deck mDeck;
- // The role of the participant.
- private Role mRole;
+ private ParticipantClient mClient = null;
- public ParticipantPeer(String userName, Deck deck, String endPoint) {
+ public ParticipantPeer(String userName, Deck deck, String serviceName) {
mUserName = userName;
mDeck = deck;
- mEndpointStr = endPoint;
- }
-
- public ParticipantPeer(String endPoint) {
- this(Unknown.USER_NAME, DeckImpl.DUMMY, endPoint);
+ mServiceName = serviceName;
}
public ParticipantPeer(String userName, Deck deck) {
- this(userName, deck, Unknown.END_POINT);
+ this(userName, deck, Unknown.SERVER_NAME);
}
- public ParticipantPeer() {
- this(Unknown.END_POINT);
- }
-
- public static Participant fromBundle(Bundle b) {
- return new ParticipantPeer(
- b.getString(B.PARTICIPANT_NAME),
- DeckImpl.fromBundle(b),
- b.getString(B.PARTICIPANT_END_POINT));
+ public ParticipantPeer(String serviceName) {
+ this(Unknown.USER_NAME, DeckImpl.DUMMY, serviceName);
}
@Override
- public String getEndPoint() {
- return mEndpointStr;
+ public String getServiceName() {
+ return mServiceName;
}
@Override
@@ -91,120 +69,64 @@
return mUserName;
}
- /**
- * TODO(jregan): Assure legal mount name (remove blanks and such).
- */
- public String getMountName() {
- return ParticipantScannerMt.ROOT_NAME + "/p_" + mDeck.getId();
- }
-
@Override
public Deck getDeck() {
return mDeck;
}
@Override
- public Bundle toBundle() {
- Bundle b = new Bundle();
- b.putSerializable(Participant.B.PARTICIPANT_ROLE, mRole);
- b.putString(B.PARTICIPANT_END_POINT, mEndpointStr);
- b.putString(B.PARTICIPANT_NAME, mUserName);
- mDeck.toBundle(b);
- return b;
- }
-
- private void unpackBundle(Bundle b) {
- mDeck = DeckImpl.fromBundle(b);
- mRole = (Role) b.get(Participant.B.PARTICIPANT_ROLE);
- mEndpointStr = b.getString(B.PARTICIPANT_END_POINT);
- mUserName = b.getString(B.PARTICIPANT_NAME);
- }
-
- @Override
public String toString() {
- return mUserName + ":" + mDeck.getTitle() +
- (mRefreshTime == null ?
- "" : ":" + mRefreshTime.toString(TIME_FMT));
+ return "[userName=\"" + mUserName +
+ "\", deck=" + mDeck +
+ ", time=" + getStringRefreshtime() + "]";
+ }
+
+ private String getStringRefreshtime() {
+ return mRefreshTime == null ?
+ "never" : mRefreshTime.toString(TIME_FMT);
}
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof Participant)) {
+ if (!(obj instanceof ParticipantPeer)) {
return false;
}
- return mEndpointStr.equals(((ParticipantPeer) obj).mEndpointStr);
+ ParticipantPeer p = (ParticipantPeer) obj;
+ return mServiceName.equals(p.mServiceName) && mDeck.equals(p.mDeck);
}
@Override
public int hashCode() {
- return mEndpointStr.hashCode() + mDeck.getTitle().hashCode();
+ return mServiceName.hashCode() + mDeck.hashCode();
}
/**
- * Make an RPC on the mEndpointStr to get title, snapshot, etc.
+ * Make an RPC on the mServiceName to get title, snapshot, etc.
*/
@Override
public void refreshData() {
- Log.d(TAG, "Refreshing data for participant " + mUserName);
- // TODO(jregan): make the rpc
- mRefreshTime = DateTime.now();
- }
+ Log.d(TAG, "Initiating refresh");
- /**
- * Binding not necessary - this service just answers requests from the
- * outside, and doesn't communicate with the parent app.
- */
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- super.onStartCommand(intent, flags, startId);
- Log.d(TAG, "onStartCommand");
- // TODO(jregan): Unpack blessings from the intent and pass them into
- // V.getPrincipal.
- unpackBundle(intent.getExtras());
- Log.d(TAG, "role = " + mRole + ", deck=" + mDeck);
- V23Manager mgr = V23Manager.Singleton.get();
- mgr.init(getApplicationContext());
- ServerImpl server = new ServerImpl(this);
- String mountName = getMountName();
- Log.d(TAG, "mountName = " + mountName);
- mEndpointStr = mgr.mount(mountName, server);
- Log.d(TAG, "Got endpoint: " + mEndpointStr);
- return START_REDELIVER_INTENT;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- V23Manager.Singleton.get().unMount();
- Log.d(TAG, "###### onDestroy");
+ if (mClient == null) {
+ Log.d(TAG, "Grabbing client.");
+ mClient = ParticipantClientFactory.getParticipantClient(
+ mServiceName);
+ Log.d(TAG, "Got client.");
+ }
+ try {
+ Log.d(TAG, "Calling get");
+ Description description = mClient.get(
+ V23Manager.Singleton.get().getVContext());
+ mDeck = new DeckImpl(description.getTitle());
+ mRefreshTime = DateTime.now();
+ Log.d(TAG, "Completed refresh.");
+ } catch (VException e) {
+ e.printStackTrace();
+ }
}
private static class Unknown {
- static final String END_POINT = "unknownEndPoint";
+ static final String SERVER_NAME = "unknownServerName";
static final String USER_NAME = "unknownUserName";
}
-
- /**
- * Implementation of VDL Participant service.
- */
- private class ServerImpl implements ParticipantServer {
- private final Participant mParticipant;
-
- public ServerImpl(Participant p) {
- mParticipant = p;
- }
-
- public Description get(VContext ctx, ServerCall call)
- throws io.v.v23.verror.VException {
- Description d = new Description();
- d.setTitle(mParticipant.getDeck().getTitle());
- d.setUserName(mParticipant.getUserName());
- return d;
- }
- }
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
index adba0c1..0d6defd 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
@@ -4,8 +4,6 @@
package io.v.android.apps.syncslides.discovery;
-import android.util.Log;
-
import java.util.HashSet;
import java.util.Set;
@@ -23,13 +21,15 @@
if (mCounter >= 2 && mCounter <= 8) {
participants.add(
new ParticipantPeer(
- "Alice", new DeckImpl("Kale - Just eat it.", null, "deckId1")));
+ "Alice", new DeckImpl(
+ "Kale - Just eat it.", null, "deckByAlice")));
}
// Bob has less to say than Alice.
if (mCounter >= 4 && mCounter <= 6) {
participants.add(
new ParticipantPeer(
- "Bob", new DeckImpl("Java - Object deluge.", null, "deckId1")));
+ "Bob", new DeckImpl(
+ "Java - Object deluge.", null, "deckByBob")));
}
return participants;
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
index 79b3533..af17b3c 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
@@ -4,6 +4,8 @@
package io.v.android.apps.syncslides.discovery;
+import android.util.Log;
+
import java.util.HashSet;
import java.util.Set;
@@ -15,27 +17,13 @@
public class ParticipantScannerMt implements ParticipantScanner {
private static final String TAG = "ParticipantScannerMt";
- /**
- * Every v23 service will be mounted in the namespace with a name prefixed
- * by this.
- */
- public static String ROOT_NAME = "users/syncslides";
-
- /**
- * Used for V23 communication.
- */
- private final V23Manager mV23Manager;
-
- public ParticipantScannerMt(V23Manager v23Manager) {
- mV23Manager = v23Manager;
- }
-
@Override
public Set<Participant> scan() {
- Set<String> endPoints = mV23Manager.scan(ROOT_NAME + "/*");
Set<Participant> result = new HashSet<>();
- for (String endPoint : endPoints) {
- result.add(new ParticipantPeer(endPoint));
+ for (String n : V23Manager.Singleton.get().scan(
+ Participant.Mt.makeScanString())) {
+ Log.d(TAG, "Found: " + n);
+ result.add(new ParticipantPeer(n));
}
return result;
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
new file mode 100644
index 0000000..fbd251a
--- /dev/null
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
@@ -0,0 +1,32 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.syncslides.discovery;
+
+import android.util.Log;
+
+import io.v.android.apps.syncslides.model.Deck;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.ServerCall;
+
+/**
+ * Serves data used in deck discovery.
+ */
+public class ParticipantServerImpl implements ParticipantServer {
+ private static final String TAG = "PresentationActivity";
+ private final Deck mDeck;
+
+ public ParticipantServerImpl(Deck d) {
+ mDeck = d;
+ }
+
+ public Description get(VContext ctx, ServerCall call)
+ throws io.v.v23.verror.VException {
+ Log.d(TAG, "Responding to Get RPC.");
+ Description d = new Description();
+ d.setTitle(mDeck.getTitle());
+ d.setUserName(mDeck.getId());
+ return d;
+ }
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
index bf4a933..306c63f 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
@@ -25,7 +25,7 @@
Duration.standardSeconds(2);
private static final Duration WAIT_BETWEEN_TASKS =
- Duration.standardSeconds(5);
+ V23Manager.MT_TIMEOUT.plus(Duration.standardSeconds(3));
private ScheduledExecutorService mTimer = null;
@@ -40,6 +40,7 @@
DELAY_BEFORE_FIRST_TASK.getMillis(),
WAIT_BETWEEN_TASKS.getMillis(),
TimeUnit.MILLISECONDS);
+ Log.d(TAG, "Done Starting");
}
void stop() {
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
index 0eb4722..1966ab3 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
@@ -4,8 +4,11 @@
package io.v.android.apps.syncslides.discovery;
+import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.util.Log;
+import android.widget.Toast;
import org.joda.time.Duration;
@@ -14,8 +17,14 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import io.v.android.libs.security.BlessingsManager;
import io.v.android.v23.V;
+import io.v.android.v23.services.blessing.BlessingCreationException;
+import io.v.android.v23.services.blessing.BlessingService;
+import io.v.impl.google.naming.NamingUtil;
import io.v.v23.context.VContext;
import io.v.v23.namespace.Namespace;
import io.v.v23.naming.Endpoint;
@@ -25,8 +34,12 @@
import io.v.v23.rpc.ListenSpec;
import io.v.v23.rpc.Server;
import io.v.v23.rpc.ServerState;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.Blessings;
+import io.v.v23.security.VPrincipal;
import io.v.v23.security.VSecurity;
import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
/**
* Does vanadium stuff - MT scanning, service creation, unmounting, etc.
@@ -37,16 +50,17 @@
* these in onCreate and onDestroy respectively.
*/
public class V23Manager {
+ public static final Duration MT_TIMEOUT =
+ Duration.standardSeconds(10);
+ public static final int BLESSING_REQUEST = 201;
private static final String TAG = "V23Manager";
-
- private static final Duration MT_TIMEOUT =
- Duration.standardSeconds(5);
- // Generates a name to use in the MT.
- private final NameGenerator mNameGenerator = new NameGeneratorByDate();
+ private static final ExecutorService mExecutor =
+ Executors.newSingleThreadExecutor();
+ private static final String MT_ADDRESS = FixedMt.PI_MILK_CRATE;
+ // private static final String MT_ADDRESS = FixedMt.JR_MOTOX;
private Context mAndroidCtx;
private VContext mBaseContext = null;
- private VContext mMTContext = null;
- private Namespace mNamespace = null;
+ private Blessings mBlessings = null;
// Can only have one of these at the moment. Could add more...
private Server mLiveServer = null;
@@ -54,41 +68,161 @@
private V23Manager() {
}
+ private static Blessings loadBlessings(Context context) {
+ Log.d(TAG, "loadBlessings from prefs");
+ try {
+ // See if there are blessings stored in shared preferences.
+ return BlessingsManager.getBlessings(context);
+ } catch (VException e) {
+ Log.w(TAG, "Cannot get blessings from prefs: " + e.getMessage());
+ }
+ return null;
+ }
+
/**
- * Placeholder for possibly scraping a website for the NS Root.
- *
+ * To be called from an Activity's onActivityResult method, e.g.
+ * public void onActivityResult(
+ * int requestCode, int resultCode, Intent data) {
+ * if (V23Manager.onActivityResult(
+ * getApplicationContext(), requestCode, resultCode, data)) {
+ * return;
+ * }
+ * }
+ */
+ public static boolean onActivityResult(
+ Context androidCtx, int requestCode, int resultCode, Intent data) {
+ Log.d(TAG, "onActivityResult");
+ if (requestCode != BLESSING_REQUEST) {
+ return false;
+ }
+ try {
+ Log.d(TAG, "unpacking blessing");
+ Blessings blessings = unpackBlessings(androidCtx, resultCode, data);
+ Singleton.get().configurePrincipal(blessings);
+ } catch (BlessingCreationException e) {
+ throw new IllegalStateException(e);
+ } catch (VException e) {
+ throw new IllegalStateException(e);
+ }
+ return true;
+ }
+
+ public static Blessings unpackBlessings(
+ Context androidCtx, int resultCode, Intent data)
+ throws BlessingCreationException, VException {
+ byte[] blessingsVom = BlessingService.extractBlessingReply(
+ resultCode, data);
+ Blessings blessings = (Blessings) VomUtil.decode(
+ blessingsVom, Blessings.class);
+ BlessingsManager.addBlessings(androidCtx, blessings);
+ Toast.makeText(androidCtx, "Got blessings", Toast.LENGTH_SHORT).show();
+ return blessings;
+ }
+
+ /**
* @return IP address of the mounttable to scan.
*/
- private static List<String> determineNamespaceRoot() {
+ public static List<String> determineNamespaceRoot() {
List<String> result = new ArrayList<>();
- result.add("/" + FixedMt.PI_MILK_CRATE);
+ result.add("/" + MT_ADDRESS);
return result;
}
- public Context getAndroidCtx() {
- return mAndroidCtx;
+ public static String syncName(String id) {
+ return NamingUtil.join("/", MT_ADDRESS, id);
}
- public void init(Context androidCtx) {
+ public VContext getVContext() {
+ return mBaseContext;
+ }
+
+ public void init(Context androidCtx, Activity activity) {
+ init(androidCtx, null, activity);
+ }
+
+ public synchronized void init(
+ Context androidCtx, Blessings otherBlessings, Activity activity) {
Log.d(TAG, "init");
if (mAndroidCtx != null) {
if (mAndroidCtx == androidCtx) {
- Log.d(TAG, "Already initialized.");
+ Log.d(TAG, "Initialization already started.");
return;
} else {
+ Log.d(TAG, "Initialization with new context.");
shutdown(Behavior.STRICT);
}
}
+ Blessings blessings = otherBlessings;
mAndroidCtx = androidCtx;
+ // Must call V.init before attempting to load blessings, so that proper
+ // code is loaded.
mBaseContext = V.init(mAndroidCtx);
- mMTContext = mBaseContext.withTimeout(MT_TIMEOUT);
- mNamespace = V.getNamespace(mMTContext);
- try {
- mNamespace.setRoots(determineNamespaceRoot());
- } catch (VException e) {
- // TODO(jregan): Handle total v23 failure higher up the stack.
- throw new IllegalStateException(e);
+ if (blessings == null) {
+ blessings = loadBlessings(androidCtx);
}
+ Namespace ns = V.getNamespace(mBaseContext);
+ try {
+ ns.setRoots(determineNamespaceRoot());
+ Log.d(TAG, "Set namespace root to: " + determineNamespaceRoot());
+ } catch (VException e) {
+ throw new IllegalStateException("Unable to set namespace.");
+ }
+ if (blessings == null) {
+ Log.d(TAG, "No blessings - firing activity " + activity.getTitle());
+ // Bail out and go get them, and re-enter init with them.
+ if (activity == null) {
+ throw new IllegalArgumentException(
+ "Cannot get blessings without an activity to return to.");
+ }
+ activity.startActivityForResult(
+ BlessingService.newBlessingIntent(androidCtx),
+ BLESSING_REQUEST);
+ return;
+ }
+ asyncConfigurePrincipal(blessings);
+ }
+
+ /**
+ * v23 operations that require a blessing (almost everything) will fail if
+ * attempted before this is true.
+ *
+ * The simplest usage is 1) There are no blessings. 2) An activity starts
+ * and calls V23Manager.init. 2) init notices there are no blessings and
+ * calls startActivityForResult 3) meanwhile, the activity and/or its
+ * components still run, but can test isBlessed before attempting anything
+ * requiring blessings. The activity will soon be re-initialized anyway. 4)
+ * user kicked over into 'account manager', gets a blessing, and the
+ * activity is restarted, this time with isBlessed == true.
+ */
+ public boolean isBlessed() {
+ return mBlessings != null;
+ }
+
+ private void configurePrincipal(final Blessings blessings) {
+ Log.d(TAG, "configurePrincipal: blessings=" +
+ (blessings == null ? "null" : blessings.toString()));
+ try {
+ VPrincipal p = V.getPrincipal(mBaseContext);
+ p.blessingStore().setDefaultBlessings(blessings);
+ p.blessingStore().set(blessings, new BlessingPattern("..."));
+ VSecurity.addToRoots(p, blessings);
+ mBlessings = blessings;
+ } catch (VException e) {
+ Log.e(TAG, String.format(
+ "Couldn't set local blessing %s: %s",
+ blessings, e.getMessage()));
+ }
+ Log.d(TAG, "blessings stored: " +
+ (mBlessings == null ? "NONE!" : mBlessings.toString()));
+ }
+
+ private void asyncConfigurePrincipal(final Blessings blessings) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ configurePrincipal(blessings);
+ }
+ });
}
public void shutdown(Behavior behavior) {
@@ -105,15 +239,40 @@
mAndroidCtx = null;
}
+ private void error(String msg) {
+ Log.e(TAG, msg);
+ Toast.makeText(mAndroidCtx, msg, Toast.LENGTH_LONG).show();
+ }
+
public Set<String> scan(String pattern) {
FirstGrabber grabber = new FirstGrabber();
scan(pattern, grabber);
return grabber.result;
}
+ /**
+ * For every server, take the first endpoint, ignore the rest.
+ */
+ private class FirstGrabber implements Visitor {
+ final HashSet<String> result = new HashSet<>();
+
+ public void visit(MountEntry entry) {
+ Log.d(TAG, " Entry: \"" + entry.getName() + "\"");
+ result.add(entry.getName());
+ final boolean logEndpoints = true;
+ if (logEndpoints) {
+ for (MountedServer server : entry.getServers()) {
+ Log.d(TAG, " endPoint: \"" + server.getServer() + "\"");
+ }
+ }
+ }
+ }
+
public void scan(String pattern, Visitor visitor) {
try {
- for (GlobReply reply : mNamespace.glob(mMTContext, pattern)) {
+ VContext ctx = mBaseContext.withTimeout(MT_TIMEOUT);
+ Namespace ns = V.getNamespace(ctx);
+ for (GlobReply reply : ns.glob(ctx, pattern)) {
if (reply instanceof GlobReply.Entry) {
visitor.visit(((GlobReply.Entry) reply).getElem());
}
@@ -124,45 +283,77 @@
}
}
- public String mount(String mountName, Object server) {
- Log.d(TAG, "mount");
- try {
- // ListenSpec spec = V.getListenSpec(mBaseContext).withProxy("proxy");
- ListenSpec spec = V.getListenSpec(mBaseContext).withAddress(
- new ListenSpec.Address("tcp", "localhost:0"));
- VContext ctx = V.withNewServer(
- V.withListenSpec(mBaseContext, spec),
- mountName,
- server,
- VSecurity.newAllowEveryoneAuthorizer());
- mLiveServer = V.getServer(ctx);
- Log.d(TAG, "Server status: " + mLiveServer.getStatus().getState());
- Endpoint[] endpoints = mLiveServer.getStatus().getEndpoints();
- Log.d(TAG, "Listening on endpoints: " + Arrays.toString(endpoints));
- if (endpoints.length < 1) {
- throw new IllegalStateException("No endpoints!");
- }
- return endpoints[0].name();
- } catch (VException e) {
- // TODO(jregan): Handle total v23 failure higher up the stack.
- throw new IllegalStateException(e);
+ private VContext getListenContext() throws VException {
+ final boolean useProxy = false;
+ // Disabled while debugging network performance / visibility issues.
+ if (useProxy) {
+ ListenSpec spec = V.getListenSpec(mBaseContext).withProxy("proxy");
+ //ListenSpec spec = V.getListenSpec(mBaseContext).withAddress(
+ // new ListenSpec.Address("tcp", "0.0.0.0:0"));
+ Log.d(TAG, "spec : " + spec.toString());
+ Log.d(TAG, "spec proxy: " + spec.getProxy().toString());
+ return V.withListenSpec(mBaseContext, spec);
}
+ return mBaseContext;
+ }
+
+ private Server makeServer(String mountName, Object server) throws VException {
+ return V.getServer(
+ V.withNewServer(
+ getListenContext(),
+ mountName,
+ server,
+ VSecurity.newAllowEveryoneAuthorizer()));
+ }
+
+ public void mount(final String mountName, final Object server) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "mounting on name \"" + mountName +
+ "\" at table " + MT_ADDRESS);
+ try {
+ mLiveServer = makeServer(mountName, server);
+ Log.d(TAG, " Server status proxies: " +
+ Arrays.deepToString(
+ mLiveServer.getStatus().getProxies()));
+ Endpoint[] points = mLiveServer.getStatus().getEndpoints();
+ for (Endpoint point : points) {
+ Log.d(TAG, " Listening on: " + point);
+ }
+ if (points.length < 1) {
+ throw new IllegalStateException("No endpoints!");
+ }
+ } catch (VException e) {
+ // TODO(jregan): java gymnastics to propagate exceptions
+ // to a callback instead of throwing over a cliff.
+ throw new IllegalStateException(e);
+ }
+ Log.d(TAG, "Done mounting on name \"" + mountName + "\"");
+ }
+ });
}
public void unMount() {
- Log.d(TAG, "unMount");
- if (mLiveServer == null) {
- throw new IllegalStateException("No v32 service");
- }
- if (mLiveServer.getStatus().getState() != ServerState.SERVER_ACTIVE) {
- throw new IllegalStateException("v32 service not active.");
- }
- try {
- mLiveServer.stop();
- } catch (VException e) {
- throw new IllegalStateException(e);
- }
- mLiveServer = null;
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "unMount");
+ if (mLiveServer == null) {
+ return;
+ }
+ if (mLiveServer.getStatus().getState() != ServerState.SERVER_ACTIVE) {
+ throw new IllegalStateException("v32 service not active.");
+ }
+ try {
+ mLiveServer.stop();
+ } catch (VException e) {
+ throw new IllegalStateException(e);
+ }
+ Log.d(TAG, "unMounted server.");
+ mLiveServer = null;
+ }
+ });
}
public enum Behavior {PERMISSIVE, STRICT}
@@ -195,19 +386,6 @@
static final String PI_MILK_CRATE = "192.168.86.254:8101";
static final String JR_LAPTOP_AT_HOME = "192.168.2.71:23000";
static final String JR_LAPTOP_VEYRON = "192.168.8.106:23000";
- }
-
- /**
- * For every server, take the first endpoint, ignore the rest.
- */
- private class FirstGrabber implements Visitor {
- final HashSet<String> result = new HashSet<>();
-
- public void visit(MountEntry entry) {
- for (MountedServer server : entry.getServers()) {
- result.add(server.getServer());
- return;
- }
- }
+ static final String JR_MOTOX = "192.168.43.136:23000";
}
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
index c8f2fb1..7965344 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
@@ -4,6 +4,10 @@
package discovery
+import (
+ "io/v/android/apps/syncslides/db"
+)
+
type Description struct {
UserName string
Title string
@@ -11,4 +15,13 @@
type Participant interface {
Get() (Description | error)
-}
\ No newline at end of file
+}
+
+// TODO(jregan): Ditch Description in favor of db.VDeck.
+// so we get thumb automatically. Rewire the remaining
+// code. Add whatever else we need.
+// This is just a test of the code generator, and should
+// be removed shortly.
+type ChuckleParticipant interface {
+ Get() (db.VDeck | error)
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
index 3570612..b90e061 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
@@ -39,6 +39,20 @@
", thumb=" + (mThumb == null ? "no" : "yes") + "]";
}
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DeckImpl)) {
+ return false;
+ }
+ DeckImpl p = (DeckImpl) obj;
+ return mDeckId.equals(p.mDeckId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDeckId.hashCode();
+ }
+
public static Deck fromBundle(Bundle b) {
if (b == null) {
throw new IllegalArgumentException("Need a bundle.");
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
index f84433c..7266789 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
@@ -20,7 +20,26 @@
* the UX. MT location determined in
* {@link io.v.android.apps.syncslides.discovery.V23Manager}.
*/
- boolean ENABLE_MT_DISCOVERY = false;
+ boolean ENABLE_MT_DISCOVERY = true;
+
+ public static class Mt {
+ /**
+ * Every v23 service will be mounted in the namespace with a name
+ * prefixed by this.
+ */
+ public static String ROOT_NAME = "liveDeck";
+
+ /**
+ * TODO(jregan): Assure legal mount name (remove blanks and such).
+ */
+ public static String makeMountName(Deck deck) {
+ return ROOT_NAME + "/" + deck.getId();
+ }
+
+ public static String makeScanString() {
+ return ROOT_NAME + "/*";
+ }
+ }
// Name of the user participating, intended to be visible to others. This
// can be a colloquial name as opposed to a 'real' name or email address
@@ -31,14 +50,11 @@
Deck getDeck();
// Name of a service with participant information.
- String getEndPoint();
+ String getServiceName();
// Initially get or refresh data from the endPoint.
void refreshData();
- // Serializable form of this for storing, passing via Message, etc.
- Bundle toBundle();
-
// For debugging.
String toString();
@@ -53,8 +69,10 @@
*/
class B {
public static final String PARTICIPANT_ROLE = "participant_role";
- public static final String PARTICIPANT_END_POINT = "participant_endPoint";
+ public static final String PARTICIPANT_SHOULD_ADV = "participant_is_advertising";
+ public static final String PARTICIPANT_SERVICE_NAME = "participant_endPoint";
public static final String PARTICIPANT_NAME = "participant_name";
+ public static final String PARTICIPANT_BLESSINGS = "participant_blessings";
public static final String PARTICIPANT_SYNCED = "participant_synced";
}
}