TBR java moments; cleanup pass to docs and ifc.
Change-Id: I74a1e51944cc05bc7bc9be17362dfef01eba4bd9
diff --git a/projects/moments/app/src/main/java/io/v/moments/ifc/Moment.java b/projects/moments/app/src/main/java/io/v/moments/ifc/Moment.java
index bf0fba1..74a5fb1 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ifc/Moment.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ifc/Moment.java
@@ -11,12 +11,13 @@
import org.joda.time.format.DateTimeFormatter;
import io.v.moments.lib.Id;
-import io.v.v23.discovery.Attributes;
/**
* A photo with ancillary information.
*/
public interface Moment extends HasId {
+ DateTimeFormatter FMT = DateTimeFormat.forPattern("yyyyMMdd_HHmmss");
+
/**
* A unique moment ID valid for the life of the app.
*/
@@ -55,8 +56,8 @@
/**
* An advertiser can be scheduled to start advertising, but not actually be
* advertising yet. There's also a lag to stop advertising. This member
- * tracks the desired eventual state, to maintain sensible UX, through
- * phone rotations and whatnot.
+ * tracks the desired eventual state, to maintain sensible UX, through phone
+ * rotations and whatnot.
*/
AdState getDesiredAdState();
@@ -70,12 +71,6 @@
*/
boolean hasPhoto(Kind kind, Style style);
-
- /**
- * From this, make a set of discovery 'attributes'.
- */
- Attributes makeAttributes();
-
/**
* Get the specified photo.
*/
@@ -104,7 +99,5 @@
HUGE, FULL, THUMB
}
- enum AdState { ON, OFF }
-
- DateTimeFormatter FMT = DateTimeFormat.forPattern("yyyyMMdd_HHmmss");
+ enum AdState {ON, OFF}
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/ifc/MomentFactory.java b/projects/moments/app/src/main/java/io/v/moments/ifc/MomentFactory.java
index 2b9dd01..6315030 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ifc/MomentFactory.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ifc/MomentFactory.java
@@ -14,17 +14,19 @@
* Makes moments, converts them to and from other formats.
*/
public interface MomentFactory {
+ Moment make(Id id, int index, String author, String caption);
+
void toPrefs(SharedPreferences.Editor editor, String prefix, Moment m);
+ Moment fromPrefs(SharedPreferences p, String prefix);
+
void toBundle(Bundle b, String prefix, Moment m);
Moment fromBundle(Bundle b, String prefix);
- Moment make(Id id, int index, String author, String caption);
+ Attributes toAttributes(Moment moment);
- Moment makeFromAttributes(Id id, int ordinal, Attributes attr);
-
- Moment fromPrefs(SharedPreferences p, String prefix);
+ Moment fromAttributes(Id id, int ordinal, Attributes attr);
enum F {
DATE, AUTHOR, CAPTION, ORDINAL, ID, ADVERTISING
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/AdConverterMoment.java b/projects/moments/app/src/main/java/io/v/moments/model/AdConverterMoment.java
index 6355ab7..3387786 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/AdConverterMoment.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/AdConverterMoment.java
@@ -76,7 +76,7 @@
if (mRemoteMomentCache.containsKey(id)) {
return mRemoteMomentCache.get(id);
}
- final Moment moment = mMomentFactory.makeFromAttributes(
+ final Moment moment = mMomentFactory.fromAttributes(
id, nextOrdinal(), descriptor.getAttrs());
mRemoteMomentCache.put(id, moment);
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
index 2b26e51..9a3a98b 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
@@ -9,6 +9,7 @@
import io.v.moments.ifc.IdSet;
import io.v.moments.ifc.Moment;
+import io.v.moments.ifc.MomentFactory;
import io.v.moments.lib.Id;
import io.v.moments.v23.ifc.Advertiser;
import io.v.moments.v23.ifc.V23Manager;
@@ -16,16 +17,24 @@
/**
* Makes moment advertisers.
*
- * More importantly, keeps a record of all of them for the life of the app. Can
- * use this record to reject local advertisements when scanning, or to shut down
- * all advertising.
+ * Keeps a record of all of them for the life of the app. This record used to
+ * reject locally created advertisements when scanning, or to shut down all
+ * advertising.
*/
public class AdvertiserFactory implements IdSet {
private final V23Manager mV23Manager;
private final Map<Id, Advertiser> mLocalAds = new HashMap<>();
+ private final MomentFactory mFactory;
- public AdvertiserFactory(V23Manager v23Manager) {
+ public AdvertiserFactory(V23Manager v23Manager, MomentFactory factory) {
+ if (v23Manager == null) {
+ throw new IllegalArgumentException("Null v23Manager");
+ }
+ if (factory == null) {
+ throw new IllegalArgumentException("Null factory");
+ }
mV23Manager = v23Manager;
+ mFactory = factory;
}
public Advertiser getOrMake(Moment moment) {
@@ -33,9 +42,7 @@
return mLocalAds.get(moment.getId());
}
Advertiser result = mV23Manager.makeAdvertiser(
- new MomentAdCampaign(moment),
- Config.Discovery.DURATION,
- Config.Discovery.NO_PATTERNS);
+ new MomentAdCampaign(moment, mFactory));
mLocalAds.put(moment.getId(), result);
return result;
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/Config.java b/projects/moments/app/src/main/java/io/v/moments/model/Config.java
index 7f07857..bbd530e 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/Config.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/Config.java
@@ -10,14 +10,9 @@
import android.os.Environment;
import android.os.Handler;
-import org.joda.time.Duration;
-
import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
import io.v.moments.R;
-import io.v.v23.security.BlessingPattern;
/**
* Configuration.
@@ -63,29 +58,4 @@
R.dimen.moment_image_width)
);
}
-
- /** Constants related to discovery. */
- public static class Discovery {
- /**
- * Required type/interface name, probably a URL into a web-based
- * ontology. Necessary for querying.
- */
- public static final String INTERFACE_NAME = "v.io/x/ref.Moments";
- /**
- * To limit scans to see only this service.
- */
- public static final String QUERY = "v.InterfaceName=\"" + INTERFACE_NAME + "\"";
-
- /**
- * After this duration an advertisement or scan for an advertisement
- * will automatically stop. Choice is arbitrary. A nice exercise would
- * be to add this to a settings menu.
- */
- public static final Duration DURATION = Duration.standardMinutes(5);
-
- /**
- * Used for public advertisements (no limits on who can see them).
- */
- public static final List<BlessingPattern> NO_PATTERNS = new ArrayList<>();
- }
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/MomentAdCampaign.java b/projects/moments/app/src/main/java/io/v/moments/model/MomentAdCampaign.java
index df2e572..443a570 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/MomentAdCampaign.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/MomentAdCampaign.java
@@ -10,47 +10,98 @@
import com.google.common.util.concurrent.ListenableFuture;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
import java.util.List;
import io.v.moments.ifc.Moment;
+import io.v.moments.ifc.MomentFactory;
import io.v.moments.v23.ifc.AdCampaign;
import io.v.v23.context.VContext;
import io.v.v23.discovery.Attachments;
-import io.v.v23.discovery.Service;
+import io.v.v23.discovery.Attributes;
import io.v.v23.rpc.ServerCall;
+import io.v.v23.security.BlessingPattern;
/**
* Makes objects that support the advertisement of a Moment.
*/
-class MomentAdCampaign implements AdCampaign {
- private static final String NO_MOUNT_NAME = "";
+public class MomentAdCampaign implements AdCampaign {
+ /**
+ * Required type/interface name, probably a URL into a web-based ontology.
+ * Necessary for querying.
+ */
+ public static final String INTERFACE_NAME = "v.io/x/ref.Moments";
+ /**
+ * To limit scans to see only this service.
+ */
+ public static final String QUERY = "v.InterfaceName=\"" + INTERFACE_NAME + "\"";
+ /**
+ * Used for public advertisements (no limits on who can see them).
+ */
+ public static final List<BlessingPattern> NO_PATTERNS = new ArrayList<>();
private final Moment mMoment;
+ private final MomentFactory mFactory;
- public MomentAdCampaign(Moment moment) {
+ public MomentAdCampaign(Moment moment, MomentFactory factory) {
+ if (moment == null) {
+ throw new IllegalArgumentException("Null moment");
+ }
+ if (factory == null) {
+ throw new IllegalArgumentException("Null factory");
+ }
mMoment = moment;
+ mFactory = factory;
}
+ @Override
+ public String getInstanceId() {
+ return mMoment.getId().toString();
+ }
+
+ @Override
+ public String getInstanceName() {
+ return mMoment.toString();
+ }
+
+ @Override
+ public String getInterfaceName() {
+ return INTERFACE_NAME;
+ }
+
+ @Override
+ public Attributes getAttributes() {
+ return mFactory.toAttributes(mMoment);
+ }
+
+ /**
+ * No attachments (empty list).
+ */
+ @Override
+ public Attachments getAttachments() {
+ return new Attachments();
+ }
+
+ /**
+ * Empty string means make no attempt to mount a server in a mount table.
+ */
+ @Override
public String getMountName() {
- return NO_MOUNT_NAME;
+ return "";
}
- public Object makeServer() {
+ @Override
+ public Object makeService() {
return new MomentServer();
}
/**
- * Makes an instance of 'Service', which is actually a service description,
- * i.e. an advertisement.
+ * A set of blessing patterns for whom this advertisement is meant; any
+ * entity not matching a pattern here won't see the advertisement.
*/
- public Service makeAdvertisement(List<String> addresses) {
- return new Service(
- mMoment.getId().toString(), /* instance Id */
- mMoment.toString(), /* instance name */
- Config.Discovery.INTERFACE_NAME, /* interface name */
- mMoment.makeAttributes(),
- addresses,
- new Attachments() /* no attachments */);
+ @Override
+ public List<BlessingPattern> getVisibility() {
+ return NO_PATTERNS;
}
/**
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java b/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
index c87d576..f341775 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
@@ -34,7 +34,7 @@
}
@Override
- public Moment makeFromAttributes(Id id, int ordinal, Attributes attr) {
+ public Moment fromAttributes(Id id, int ordinal, Attributes attr) {
return new MomentImpl(
mBitMapper, id, ordinal,
attr.get(F.AUTHOR.toString()),
@@ -44,6 +44,16 @@
}
@Override
+ public Attributes toAttributes(Moment moment) {
+ Attributes attr = new Attributes();
+ attr.put(MomentFactory.F.AUTHOR.toString(), moment.getAuthor());
+ attr.put(MomentFactory.F.CAPTION.toString(), moment.getCaption());
+ attr.put(MomentFactory.F.DATE.toString(),
+ Moment.FMT.print(moment.getCreationTime()));
+ return attr;
+ }
+
+ @Override
public void toBundle(Bundle b, String prefix, Moment m) {
KeyMaker km = new KeyMaker(prefix);
b.putString(km.get(F.ID), m.getId().toString());
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/MomentImpl.java b/projects/moments/app/src/main/java/io/v/moments/model/MomentImpl.java
index 33e22a0..ed521cd 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/MomentImpl.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/MomentImpl.java
@@ -8,15 +8,13 @@
import org.joda.time.DateTime;
-import io.v.moments.ifc.MomentFactory;
-import io.v.moments.lib.Id;
import io.v.moments.ifc.Moment;
-import io.v.v23.discovery.Attributes;
+import io.v.moments.lib.Id;
/**
* A photo and ancillary information.
*/
-public class MomentImpl implements Moment {
+class MomentImpl implements Moment {
private static final String NOT_LETTERS_DIGITS = "[^a-zA-Z0-9]";
protected final BitMapper mBitMapper;
private final DateTime mCreationTime;
@@ -47,15 +45,6 @@
mDesiredAdState = value;
}
- @Override
- public Attributes makeAttributes() {
- Attributes attr = new Attributes();
- attr.put(MomentFactory.F.AUTHOR.toString(), getAuthor());
- attr.put(MomentFactory.F.CAPTION.toString(), getCaption());
- attr.put(MomentFactory.F.DATE.toString(), FMT.print(getCreationTime()));
- return attr;
- }
-
public boolean hasPhoto(Kind kind, Style style) {
return mBitMapper.exists(getOrdinal(), kind, style);
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/DividerItemDecoration.java b/projects/moments/app/src/main/java/io/v/moments/ux/DividerItemDecoration.java
index bc2a56a..507c6fc 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/DividerItemDecoration.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/DividerItemDecoration.java
@@ -31,7 +31,7 @@
import android.support.v7.widget.RecyclerView;
import android.view.View;
-public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+class DividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java b/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
index d6c17ed..1c85820 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
@@ -45,6 +45,7 @@
import io.v.moments.model.AdvertiserFactory;
import io.v.moments.model.BitMapper;
import io.v.moments.model.Config;
+import io.v.moments.model.MomentAdCampaign;
import io.v.moments.model.MomentFactoryImpl;
import io.v.moments.model.StateStore;
import io.v.moments.model.Toaster;
@@ -188,12 +189,12 @@
// Compresses byte data, converts byte[] to bitmap, manages file storage.
mBitMapper = Config.makeBitmapper(this);
- // Makes advertisers. Needs v23Manager to do advertising.
- mAdvertiserFactory = new AdvertiserFactory(mV23Manager);
-
// Makes moments. Each moment needs a bitmapper to read its BitMaps.
mMomentFactory = new MomentFactoryImpl(mBitMapper);
+ // Makes advertisers. Needs v23Manager to do advertising.
+ mAdvertiserFactory = new AdvertiserFactory(mV23Manager, mMomentFactory);
+
// Local moments, with photos taken by the local device.
mLocalMoments = new ObservedList<>();
@@ -212,8 +213,7 @@
Toaster toaster = new Toaster(this);
- mScanner = mV23Manager.makeScanner(
- Config.Discovery.QUERY, Config.Discovery.DURATION);
+ mScanner = mV23Manager.makeScanner(MomentAdCampaign.QUERY);
mScanSwitchHolder = new ScanSwitchHolder(
toaster, mScanner, mRemoteMoments);
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java b/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
index 3950771..b85050f 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
@@ -5,7 +5,6 @@
package io.v.moments.ux;
import android.content.Context;
-import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -21,7 +20,7 @@
/**
* Stacks two moment lists in a recycler view.
*/
-public class MomentAdapter extends RecyclerView.Adapter<MomentHolder>
+class MomentAdapter extends RecyclerView.Adapter<MomentHolder>
implements ListObserver {
private final ObservedList<Moment> mRemoteMoments;
private final ObservedList<Moment> mLocalMoments;
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java b/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
index 9b86be3..df8b18a 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
@@ -16,22 +16,29 @@
import com.google.common.util.concurrent.FutureCallback;
+import org.joda.time.Duration;
+
import java.util.concurrent.CancellationException;
import io.v.moments.R;
-import io.v.moments.v23.ifc.Advertiser;
import io.v.moments.ifc.Moment;
import io.v.moments.ifc.Moment.Kind;
import io.v.moments.ifc.Moment.Style;
import io.v.moments.model.Toaster;
+import io.v.moments.v23.ifc.Advertiser;
import static io.v.moments.ifc.Moment.AdState;
/**
* Holds the views comprising a Moment for a RecyclerView.
*/
-public class MomentHolder extends RecyclerView.ViewHolder {
+class MomentHolder extends RecyclerView.ViewHolder {
private static final String TAG = "MomentHolder";
+ /**
+ * After this duration a advertisement automatically stop. Choice is
+ * arbitrary.
+ */
+ private static final Duration DURATION = Duration.standardMinutes(5);
private final TextView mAuthorTextView;
private final TextView mCaptionTextView;
private final SwitchCompat mAdvertiseButton;
@@ -105,7 +112,8 @@
if (!advertiser.isAdvertising()) {
advertiser.start(
makeAdvertiseStartCallback(moment),
- makeAdvertiseStopCallback(moment));
+ makeAdvertiseStopCallback(moment),
+ DURATION);
} else {
Log.d(TAG, "Advertiser already on.");
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/ScanSwitchHolder.java b/projects/moments/app/src/main/java/io/v/moments/ux/ScanSwitchHolder.java
index 003653a..bb660d5 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/ScanSwitchHolder.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/ScanSwitchHolder.java
@@ -10,6 +10,8 @@
import com.google.common.util.concurrent.FutureCallback;
+import org.joda.time.Duration;
+
import java.util.concurrent.CancellationException;
import io.v.moments.ifc.Moment;
@@ -23,8 +25,12 @@
* The callbacks provided will be run on the UX thread, so its safe to perform
* UX operations in the callbacks.
*/
-public class ScanSwitchHolder implements CompoundButton.OnCheckedChangeListener {
+class ScanSwitchHolder implements CompoundButton.OnCheckedChangeListener {
private static final String TAG = "ScanSwitchHolder";
+ /**
+ * After this duration a scan automatically stop. Choice is arbitrary.
+ */
+ private static final Duration DURATION = Duration.standardMinutes(5);
private final Scanner mScanner;
private final Toaster mToaster;
private final DiscoveredList<Moment> mRemoteMoments;
@@ -62,7 +68,8 @@
makeStartupCallback(),
mRemoteMoments,
mRemoteMoments,
- makeCompletionCallback());
+ makeCompletionCallback(),
+ DURATION);
} else {
if (!mScanner.isScanning()) {
Log.d(TAG, "Asked to stop scanning, but already not scanning.");
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdCampaign.java b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdCampaign.java
index e8a329a..bc8155d 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdCampaign.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdCampaign.java
@@ -6,27 +6,64 @@
import java.util.List;
-import io.v.v23.discovery.Service;
+import io.v.v23.discovery.Attachments;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.security.BlessingPattern;
/**
- * Makes objects that support advertising.
+ * Provides the data needed to run an advertisement.
*/
public interface AdCampaign {
/**
- * Makes an instance of a service that will be run during the life of the
- * advertisement.
+ * Unique Id associated with the advertisement, used to discriminate when an
+ * ad is found or lost.
*/
- Object makeServer();
+ String getInstanceId();
/**
- * Name at which the service should be mounted. Can be empty.
+ * Optional human readable name, can be used in query discrimination.
+ */
+ String getInstanceName();
+
+ /**
+ * Optional service interface name, can be used in query discrimination. The
+ * name, if provided, should be the name of the service interface associated
+ * with the result of #makeServer().
+ */
+ String getInterfaceName();
+
+ /**
+ * Map of 'smallish' name/value pairs to send with the advertisement.
+ */
+ Attributes getAttributes();
+
+ /**
+ * Larger blobs of data to made available asynchronously to scanners.
+ */
+ Attachments getAttachments();
+
+ /**
+ * Makes an instance of a service (a set of handlers) that will be run
+ * during the life of the advertisement.
+ *
+ * I.e., every time an advertisement is started using this campaign, this
+ * factory method will be called to create a new service object with a clean
+ * state. The service object will be used to start an actual server that
+ * will serve requests only as long as the advertisement.
+ *
+ * If null returned, no server is launched.
+ */
+ Object makeService();
+
+ /**
+ * Name at which the service associated with #makeService() should be
+ * mounted. Can be empty.
*/
String getMountName();
/**
- * Makes an instance of 'Service', which is actually a service description,
- * i.e. an advertisement. The argument is the list of real addresses at
- * which the service can be found (presumes no mount name).
+ * A set of blessing patterns for whom this advertisement is meant; any
+ * entity not matching a pattern here won't see the advertisement.
*/
- Service makeAdvertisement(List<String> addresses);
+ List<BlessingPattern> getVisibility();
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdConverter.java b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdConverter.java
index 26e59b8..24db2ea 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdConverter.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/AdConverter.java
@@ -7,13 +7,12 @@
import io.v.v23.discovery.Service;
/**
- * The io.v.v23.discovery.Service isn't a service, it's the *description* of a
- * service used as a discovery advertisement.
- *
* Implementations of this interface construct an instance of T from the
- * attributes in Service, and/or by making an RPC to the real underlying service
- * described by the advertisement to get data needed to make a T.
+ * attributes in the advertisement, and/or by making RPCs to services associated
+ * with or otherwise mentioned by the advertisement.
+ *
+ * TODO(jregan): This method should return ListenableFuture<T>.
*/
public interface AdConverter<T> {
- T make(Service service);
+ T make(Service advertisement);
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Advertiser.java b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Advertiser.java
index d6f7631..10c5c2d 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Advertiser.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Advertiser.java
@@ -6,8 +6,12 @@
import com.google.common.util.concurrent.FutureCallback;
+import org.joda.time.Duration;
+
/**
- * Advertiser controls, intended to be similar to Scanner controls.
+ * Advertiser - can start, stop, and restart advertisements.
+ *
+ * The start and stop cycle is appropriate for connection to a toggle button.
*/
public interface Advertiser {
/**
@@ -15,14 +19,17 @@
*
* Callbacks can be expected to run on the UX thread.
*
- * @param onStart executed on success or failure of advertising startup.
- *
- * @param onStop executed on success or failure of advertising completion.
- * An advertisement might shutdown for reasons other than a
- * call to stop, e.g. a timeout.
+ * @param onStart Callback with success or failure handlers for advertiser
+ * startup. A success can switch a toggle button to "on".
+ * @param onStop Callback with success or failure handlers for advertiser
+ * shutdown. An advertiser might shutdown for reasons other
+ * than a call to stop, e.g. a timeout. The callback can,
+ * say, switch a toggle button back to "off".
+ * @param timeout Amount of time until the advertisement self-cancels.
*/
void start(FutureCallback<Void> onStart,
- FutureCallback<Void> onStop);
+ FutureCallback<Void> onStop,
+ Duration timeout);
/**
* True if stop could usefully be called.
@@ -30,8 +37,8 @@
boolean isAdvertising();
/**
- * Synchronously stop advertising. Should result in execution of
- * completionCallback.
+ * Synchronously stop advertising. Should result in execution of onStop
+ * callback.
*/
void stop();
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Scanner.java b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Scanner.java
index 35ec1d7..057f42d 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Scanner.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/Scanner.java
@@ -6,8 +6,12 @@
import com.google.common.util.concurrent.FutureCallback;
+import org.joda.time.Duration;
+
/**
- * Scanner controls, intended to be similar to Advertiser controls.
+ * Scanner - can start, stop, and restart a scan for advertisements.
+ *
+ * The start and stop cycle is appropriate for connection to a toggle button.
*/
public interface Scanner {
/**
@@ -15,17 +19,24 @@
*
* Callbacks can be expected to run on the UX thread.
*
- * @param onStart executed on success or failure of scan startup.
- * @param foundListener executed on each found advertisement.
- * @param lostListener executed on each lost advertisement.
- * @param onStop executed on success or failure of scan completion. A
- * scan might shutdown for reasons other than a call to
- * stop, e.g. a timeout.
+ * @param onStart Callback with success or failure handlers for scan
+ * startup. A success can switch a toggle button to
+ * "on".
+ * @param foundListener Handler executed on each found newly found
+ * advertisement.
+ * @param lostListener Handler executed on each previously seen but now
+ * lost advertisement.
+ * @param onStop Callback with success or failure handlers for scan
+ * shutdown. A scan might shutdown for reasons other
+ * than a call to stop, e.g. a timeout. The callback
+ * can, say, switch a toggle button back to "off".
+ * @param timeout Amount of time until the scan self-cancels.
*/
void start(FutureCallback<Void> onStart,
AdvertisementFoundListener foundListener,
AdvertisementLostListener lostListener,
- FutureCallback<Void> onStop);
+ FutureCallback<Void> onStop,
+ Duration timeout);
/**
* True if stop could usefully be called.
@@ -33,8 +44,8 @@
boolean isScanning();
/**
- * Synchronously stop scanning. Should result in execution of
- * completionCallback.
+ * Synchronously stop scanning. Should result in execution of onStop
+ * callback.
*/
void stop();
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/V23Manager.java b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/V23Manager.java
index cb0ccfe..00949e1 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/ifc/V23Manager.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/ifc/V23Manager.java
@@ -10,45 +10,57 @@
import org.joda.time.Duration;
-import java.util.List;
-
import io.v.v23.context.VContext;
-import io.v.v23.security.BlessingPattern;
import io.v.v23.security.Blessings;
/**
- * V23 functionality; service creation and discovery.
+ * Secure distributed computing via underlying v23 APIs.
+ *
+ * This and other interfaces in the encompassing package comprise an API that
+ * might feel more comfortable to java Android developers. It wraps the
+ * underlying static v23 methods in a framework of injectable, mockable
+ * instances.
*/
public interface V23Manager {
/**
* Start V23 runtime bound to the given activity, and give it a callback via
- * which it will get its blessings.
+ * which it will get its blessings. This should be called on onCreate().
+ *
+ * When the blessings come in, the app can safely use v23 operations that
+ * require a notion of identity, but until that time should make no attempt
+ * to do so.
*/
- void init(Activity activity,
- FutureCallback<Blessings> blessingCallback);
+ void init(Activity activity, FutureCallback<Blessings> blessingCallback);
/**
- * Shutdown the v23 runtime. This should be called in onDestroy to clean up
- * any lingering contexts associated with advertising or scanning.
+ * Shutdown the v23 runtime. This should be called in onDestroy() to cancel
+ * any lingering contexts associated with v23 operations (advertising,
+ * scanning, serving etc.), so that a subsequent call to init - say, during
+ * destroy/create lifecycle event series - will start with clean state in
+ * the v23 runtime.
*/
void shutdown();
/**
- * Used to construct RPCs.
+ * Used by v23 clients to make v23 RPCs.
+ *
+ * @param duration Amount of time until the operation self-cancels.
*/
- VContext contextWithTimeout(Duration timeout);
+ VContext contextWithTimeout(Duration duration);
/**
- * Returns an advertiser that will start advertising using the adCampaign
- * for a fixed time duration.
+ * Returns an advertiser bound to the given adCampaign.
+ *
+ * @param adCampaign Immutable description of the ad to run.
+ * @return Advertiser that can start, stop and restart the advertisement.
*/
- Advertiser makeAdvertiser(AdCampaign adCampaign,
- Duration duration,
- List<BlessingPattern> visibility);
+ Advertiser makeAdvertiser(AdCampaign adCampaign);
/**
- * Returns a scanner that will look for advertisements matching the query,
- * for a fixed time duration.
+ * Returns a scanner that will look for advertisements matching the query.
+ *
+ * @param query Query limiting the ads that are processed.
+ * @return Scanner that can start, stop and restart the scan.
*/
- Scanner makeScanner(String query, Duration duration);
+ Scanner makeScanner(String query);
}
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/impl/V23ManagerImpl.java b/projects/moments/app/src/main/java/io/v/moments/v23/impl/V23ManagerImpl.java
index 2df557c..b64e262 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/impl/V23ManagerImpl.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/impl/V23ManagerImpl.java
@@ -28,10 +28,10 @@
import io.v.v23.InputChannelCallback;
import io.v.v23.InputChannels;
import io.v.v23.context.VContext;
+import io.v.v23.discovery.Service;
import io.v.v23.discovery.Update;
import io.v.v23.discovery.VDiscovery;
import io.v.v23.naming.Endpoint;
-import io.v.v23.security.BlessingPattern;
import io.v.v23.security.Blessings;
import io.v.v23.security.VSecurity;
import io.v.v23.verror.VException;
@@ -120,15 +120,13 @@
}
@Override
- public Advertiser makeAdvertiser(AdCampaign adCampaign,
- Duration duration,
- List<BlessingPattern> visibility) {
- return new AdvertiserImpl(adCampaign, duration, visibility);
+ public Advertiser makeAdvertiser(AdCampaign adCampaign) {
+ return new AdvertiserImpl(adCampaign);
}
@Override
- public Scanner makeScanner(String query, Duration duration) {
- return new ScannerImpl(query, duration);
+ public Scanner makeScanner(String query) {
+ return new ScannerImpl(query);
}
public static class Singleton {
@@ -158,33 +156,22 @@
private static final String TAG = "AdvertiserImpl";
private final AdCampaign mAdCampaign;
- private final Duration mDuration;
- private final List<BlessingPattern> mVisibility;
private VContext mAdvCtx;
private VContext mServerCtx;
+ private Duration mDuration;
- public AdvertiserImpl(
- AdCampaign adCampaign,
- Duration duration,
- List<BlessingPattern> visibility) {
+ public AdvertiserImpl(AdCampaign adCampaign) {
if (adCampaign == null) {
throw new IllegalArgumentException("Null adCampaign");
}
- if (duration == null) {
- throw new IllegalArgumentException("Null duration");
- }
- if (visibility == null) {
- throw new IllegalArgumentException("Null visibility");
- }
mAdCampaign = adCampaign;
- mDuration = duration;
- mVisibility = visibility;
}
@Override
public void start(FutureCallback<Void> onStartCallback,
- FutureCallback<Void> onStopCallback) {
+ FutureCallback<Void> onStopCallback,
+ Duration timeout) {
Log.d(TAG, "Entering start.");
if (isAdvertising()) {
onStartCallback.onFailure(
@@ -192,6 +179,11 @@
return;
}
+ if (timeout == null) {
+ throw new IllegalArgumentException("Null timeout");
+ }
+ mDuration = timeout;
+
if (mDiscovery == null) {
onStartCallback.onFailure(
new IllegalStateException("Discovery not ready."));
@@ -201,7 +193,7 @@
try {
mServerCtx = makeServerContext(
mAdCampaign.getMountName(),
- mAdCampaign.makeServer());
+ mAdCampaign.makeService());
} catch (VException e) {
onStartCallback.onFailure(
new IllegalStateException("Unable to start service.", e));
@@ -211,12 +203,19 @@
VContext context = contextWithTimeout(mDuration);
+ Service advertisement = new Service(
+ mAdCampaign.getInstanceId(),
+ mAdCampaign.getInstanceName(),
+ mAdCampaign.getInterfaceName(),
+ mAdCampaign.getAttributes(),
+ makeServerAddressList(mServerCtx),
+ mAdCampaign.getAttachments());
+
ListenableFuture<ListenableFuture<Void>> nestedFuture =
mDiscovery.advertise(
context,
- mAdCampaign.makeAdvertisement(
- makeServerAddressList(mServerCtx)),
- mVisibility);
+ advertisement,
+ mAdCampaign.getVisibility());
Futures.addCallback(
nestedFuture,
@@ -328,17 +327,13 @@
class ScannerImpl implements Scanner {
private static final String TAG = "ScannerImpl";
private final String mQuery;
- private final Duration mDuration;
+ private Duration mDuration;
private VContext mScanCtx;
- public ScannerImpl(String query, Duration duration) {
+ public ScannerImpl(String query) {
if (query == null || query.isEmpty()) {
throw new IllegalArgumentException("Empty query.");
}
- if (duration == null) {
- throw new IllegalArgumentException("Null duration.");
- }
- mDuration = duration;
mQuery = query;
}
@@ -352,14 +347,18 @@
FutureCallback<Void> onStart,
AdvertisementFoundListener foundListener,
AdvertisementLostListener lostListener,
- FutureCallback<Void> onStop) {
+ FutureCallback<Void> onStop,
+ Duration timeout) {
Log.d(TAG, "Entering start.");
if (isScanning()) {
onStart.onFailure(
new IllegalStateException("Already scanning."));
return;
}
-
+ if (timeout == null) {
+ throw new IllegalArgumentException("Null timeout.");
+ }
+ mDuration = timeout;
Log.d(TAG, "Starting scan with q=[" + mQuery + "]");
if (mDiscovery == null) {
onStart.onFailure(
diff --git a/projects/moments/app/src/main/java/io/v/moments/v23/package-info.java b/projects/moments/app/src/main/java/io/v/moments/v23/package-info.java
index 9faee0b..e658636 100644
--- a/projects/moments/app/src/main/java/io/v/moments/v23/package-info.java
+++ b/projects/moments/app/src/main/java/io/v/moments/v23/package-info.java
@@ -3,9 +3,9 @@
// license that can be found in the LICENSE file.
/**
- * Below this point sits code that wraps the evolving v23 API.
+ * Below this point sits code that wraps the underlying, evolving v23 API.
*
- * None of it depends on any 'moments' code/data.
+ * None of it depends on 'moments'-specific code/data.
*
* The wrapper facilitates the use of the underlying API, and makes it easier to
* test classes that use the API.
diff --git a/projects/moments/app/src/test/java/io/v/moments/model/AdConverterMomentTest.java b/projects/moments/app/src/test/java/io/v/moments/model/AdConverterMomentTest.java
index 65ee128..1097ca0 100644
--- a/projects/moments/app/src/test/java/io/v/moments/model/AdConverterMomentTest.java
+++ b/projects/moments/app/src/test/java/io/v/moments/model/AdConverterMomentTest.java
@@ -87,7 +87,7 @@
when(mMoment.getId()).thenReturn(ID);
when(mAdvertisement.getAttrs()).thenReturn(mAttributes);
when(mAdvertisement.getAddrs()).thenReturn(ADDRESSES);
- when(mMomentFactory.makeFromAttributes(
+ when(mMomentFactory.fromAttributes(
eq(ID), anyInt(), eq(mAttributes))).thenReturn(mMoment);
when(mClientFactory.makeClient(eq("/" + ADDRESS0))).thenReturn(mClient);
when(mV23Manager.contextWithTimeout(
@@ -124,7 +124,7 @@
// Make the moment - this is the call being tested.
assertEquals(mMoment, mConverter.make(mAdvertisement));
- verify(mMomentFactory).makeFromAttributes(
+ verify(mMomentFactory).fromAttributes(
eq(ID), mOrdinal.capture(), eq(mAttributes));
// The ordinal value supplied to the factory should be one.
diff --git a/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserFactoryTest.java b/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserFactoryTest.java
index 4dfeafc..68f1672 100644
--- a/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserFactoryTest.java
+++ b/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserFactoryTest.java
@@ -17,22 +17,21 @@
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
import io.v.moments.ifc.Moment;
+import io.v.moments.ifc.MomentFactory;
import io.v.moments.lib.Id;
import io.v.moments.v23.ifc.AdCampaign;
import io.v.moments.v23.ifc.Advertiser;
import io.v.moments.v23.ifc.V23Manager;
-import io.v.v23.security.BlessingPattern;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,15 +44,15 @@
public ExpectedException mThrown = ExpectedException.none();
@Captor
- ArgumentCaptor<MomentAdCampaign> mSupporter;
+ ArgumentCaptor<AdCampaign> mCampaign;
@Captor
ArgumentCaptor<Duration> mDuration;
- @Captor
- ArgumentCaptor<List<BlessingPattern>> mBlessings;
@Mock
V23Manager mV23Manager;
@Mock
+ MomentFactory mMomentFactory;
+ @Mock
Moment mMoment;
@Mock
Advertiser mAdvertiser0;
@@ -64,12 +63,9 @@
@Before
public void setup() throws Exception {
- mFactory = new AdvertiserFactory(mV23Manager);
+ mFactory = new AdvertiserFactory(mV23Manager, mMomentFactory);
when(mV23Manager.makeAdvertiser(
- any(AdCampaign.class),
- eq(Config.Discovery.DURATION),
- eq(Config.Discovery.NO_PATTERNS)
- )).thenReturn(mAdvertiser0);
+ any(AdCampaign.class))).thenReturn(mAdvertiser0);
}
@Test
@@ -84,11 +80,9 @@
assertEquals(a0, iter.next());
assertFalse(iter.hasNext());
- verify(mV23Manager).makeAdvertiser(
- mSupporter.capture(), mDuration.capture(), mBlessings.capture());
+ verify(mV23Manager).makeAdvertiser(mCampaign.capture());
- assertEquals(Config.Discovery.DURATION, mDuration.getValue());
- assertSame(Config.Discovery.NO_PATTERNS, mBlessings.getValue());
+ assertNotNull(mCampaign.getValue());
}
@Test
@@ -98,10 +92,7 @@
when(mMoment.getId()).thenReturn(ID1);
when(mV23Manager.makeAdvertiser(
- any(AdCampaign.class),
- eq(Config.Discovery.DURATION),
- eq(Config.Discovery.NO_PATTERNS)
- )).thenReturn(mAdvertiser1);
+ any(AdCampaign.class))).thenReturn(mAdvertiser1);
Advertiser a1 = mFactory.getOrMake(mMoment);
diff --git a/projects/moments/app/src/test/java/io/v/moments/model/MomentAdCampaignTest.java b/projects/moments/app/src/test/java/io/v/moments/model/MomentAdCampaignTest.java
new file mode 100644
index 0000000..dce6e6a
--- /dev/null
+++ b/projects/moments/app/src/test/java/io/v/moments/model/MomentAdCampaignTest.java
@@ -0,0 +1,105 @@
+// 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.moments.model;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.joda.time.DateTime;
+
+import io.v.moments.ifc.Moment;
+import io.v.moments.ifc.MomentFactory;
+import io.v.moments.lib.Id;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.rpc.ServerCall;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MomentAdCampaignTest {
+ static final Id ID = Id.makeRandom();
+ static final String PIZZA = "pizza";
+ static final String AUTHOR = "shake a spear";
+ static final String CAPTION = "If we pull this off, we'll eat like kings.";
+ static final DateTime CREATION_TIME = DateTime.now();
+
+ @Rule
+ public ExpectedException mThrown = ExpectedException.none();
+
+ @Mock
+ MomentFactory mMomentFactory;
+ @Mock
+ Moment mMoment;
+ @Mock
+ Attributes mAttributes;
+ @Mock
+ VContext mCtx;
+ @Mock
+ ServerCall mCall;
+
+ MomentAdCampaign mCampaign;
+
+ @Before
+ public void setup() throws Exception {
+ when(mMoment.getId()).thenReturn(ID);
+ when(mMoment.toString()).thenReturn(PIZZA);
+ when(mMoment.getCaption()).thenReturn(CAPTION);
+ when(mMoment.getAuthor()).thenReturn(AUTHOR);
+ when(mMoment.getCreationTime()).thenReturn(CREATION_TIME);
+ when(mMomentFactory.toAttributes(mMoment)).thenReturn(mAttributes);
+ mCampaign = new MomentAdCampaign(mMoment, mMomentFactory);
+ }
+
+ @Test
+ public void makeWithoutMomentThrowsException() {
+ mThrown.expect(IllegalArgumentException.class);
+ mThrown.expectMessage("Null moment");
+ mCampaign = new MomentAdCampaign(null, mMomentFactory);
+ }
+
+ @Test
+ public void makeWithoutFactoryThrowsException() {
+ mThrown.expect(IllegalArgumentException.class);
+ mThrown.expectMessage("Null factory");
+ mCampaign = new MomentAdCampaign(mMoment, null);
+ }
+
+ @Test
+ public void emptyMountName() throws Exception {
+ assertEquals("", mCampaign.getMountName());
+ }
+
+ @Test
+ public void properInterfaceName() throws Exception {
+ assertEquals(
+ MomentAdCampaign.INTERFACE_NAME, mCampaign.getInterfaceName());
+ }
+
+ @Test
+ public void factoryMakesAttributes() throws Exception {
+ assertSame(mAttributes, mCampaign.getAttributes());
+ }
+
+ /**
+ * TODO(jregan): Service needs more coverage.
+ */
+ @Test
+ public void checkService() throws Exception {
+ MomentIfcServer server = (MomentIfcServer) mCampaign.makeService();
+ assertNotNull(server);
+ MomentWireData data = server.getBasics(mCtx, mCall).get();
+ assertEquals(AUTHOR, data.getAuthor());
+ assertEquals(CAPTION, data.getCaption());
+ assertEquals(CREATION_TIME.getMillis(), data.getCreationTime());
+ }
+}