discovery: Added a flutter example for discovery

The UI is pretty awful here, but I figure I should check this in since
it works and clean up the UI later.  There is no v23proxy support as of
yet because it requires a newer version of mojo than flutter.

Change-Id: I890fa26c8fdf1b8b4a0921b791f7f503d98fe01f
diff --git a/.gitignore b/.gitignore
index ec6cb21..3899f02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
 /java/.gradle
 /java/build/
 /java/local.properties
+*.srcjar
diff --git a/example/discovery/.gitignore b/example/discovery/.gitignore
new file mode 100644
index 0000000..a7541f8
--- /dev/null
+++ b/example/discovery/.gitignore
@@ -0,0 +1,4 @@
+.pub/
+.packages/
+build/
+packages
diff --git a/example/discovery/FLUTTER_VERSION b/example/discovery/FLUTTER_VERSION
new file mode 100644
index 0000000..b01d319
--- /dev/null
+++ b/example/discovery/FLUTTER_VERSION
@@ -0,0 +1 @@
+b70a53807aca5c74c48298f68fcb6f041fedbe9a
diff --git a/example/discovery/Makefile b/example/discovery/Makefile
new file mode 100644
index 0000000..b8484e0
--- /dev/null
+++ b/example/discovery/Makefile
@@ -0,0 +1,77 @@
+ifndef DEVICE_NUM
+	DEVICE_NUM := 1
+endif
+
+DEVICE_NUM_PLUS_ONE := $(shell echo $(DEVICE_NUM) \+ 1 | bc)
+DEVICE_ID := $(shell adb devices | sed -n $(DEVICE_NUM_PLUS_ONE)p | awk '{ print $$1; }')
+DEVICE_FLAG := --target-device $(DEVICE_ID)
+
+ifneq ($(DEVICE_NUM), 1)
+	REUSE_FLAG := --reuse-servers
+endif
+
+ifdef VLOG
+	VLOG_FLAGS = --v=$(VLOG) --logtostderr=true
+endif
+
+MOJO_DEVTOOLS := $(shell jiri v23-profile env --profiles=mojo --target=arm-android MOJO_DEVTOOLS=)
+MOJO_SHELL := $(shell jiri v23-profile env --profiles=mojo --target=arm-android MOJO_SHELL=)
+
+APP_FLX_FILE := $(PWD)/build/app.flx
+DISCOVERY_MOJO_DIR := $(PWD)/packages/v23discovery/mojo_services/android
+MOJO_SHELL_CMD_PATH := /data/local/tmp/org.chromium.mojo.shell.cmd
+
+default: run
+
+.PHONY: dartanalyzer
+dartanalyzer: packages
+	dartanalyzer lib/main.dart
+
+.PHONY: dartfmt
+dartfmt: packages
+	dartfmt --overwrite lib
+
+packages: pubspec.yaml
+	pub get
+
+.PHONY: upgrade-packages
+upgrade-packages:
+	pub upgrade
+
+.PHONY: build
+build: packages
+	pub run flutter_tools build
+
+.PHONY: install-shell
+install-shell:
+	adb -s $(DEVICE_ID) install $(MOJO_SHELL)
+
+.PHONY: uninstall-shell
+uninstall-shell:
+	adb -s $(DEVICE_ID) uninstall org.chromium.mojo.shell
+
+# Usage example:
+# DEVICE_NUM=1 make run
+# DEVICE_NUM=2 make run
+run: build install-shell
+	pub run flutter_tools run_mojo \
+	--devtools-path $(MOJO_DEVTOOLS)/mojo_run \
+	--android --mojo-debug -- --enable-multiprocess \
+	--map-origin="https://discovery.mojo.v.io/=$(DISCOVERY_MOJO_DIR)" \
+	$(DEVICE_FLAG) \
+	$(REUSE_FLAG) \
+	--no-config-file
+
+# Helper targets
+run1:
+	DEVICE_NUM=1 make run
+run2:
+	DEVICE_NUM=2 make run
+run3:
+	DEVICE_NUM=3 make run
+run4:
+	DEVICE_NUM=4 make run
+
+.PHONY: clean
+clean:
+	rm -rf packages
diff --git a/example/discovery/README.md b/example/discovery/README.md
new file mode 100644
index 0000000..a24b03d
--- /dev/null
+++ b/example/discovery/README.md
@@ -0,0 +1,32 @@
+# Discovery
+
+A simple Flutter app that uses v23 discovery.
+
+# Prerequisites
+
+## Flutter
+
+A clone of https://github.com/flutter/flutter/ at the commit # specified in FLUTTER_VERSION file must be available in a directory
+called `flutter` at the same level as $JIRI_ROOT directory.
+
+## Mojo
+
+Mojo profile for Android target must be installed. You can run `jiri v23-profile install --target=arm-android mojo` to install it.
+
+## Dart
+
+Mojo profile must be installed. You can run `jiri v23-profile install dart` to install it.
+
+## Android Setup
+
+Currently Flutter requires an Android device running the Lollipop (or newer) version of the Android operating system.
+`adb` tool from Android SDK needs to be installed. Please follow instructions on setting up your android device [here](http://flutter.io/getting-started/#setting-up-your-android-device)
+
+# Running Discovery Demo
+
+Connect your Android device via USB and ensure `Android debugging` is enabled, then execute:
+```
+make run
+```
+
+If you have multiple Android devices connected, you can set the DEVICE_NUM enviroment variable to choose the device
diff --git a/example/discovery/flutter.yaml b/example/discovery/flutter.yaml
new file mode 100644
index 0000000..8f711f3
--- /dev/null
+++ b/example/discovery/flutter.yaml
@@ -0,0 +1,4 @@
+name: discovery
+material-design-icons:
+  - name: notification/tap_and_play
+  - name: action/search
diff --git a/example/discovery/lib/main.dart b/example/discovery/lib/main.dart
new file mode 100644
index 0000000..7cb605e
--- /dev/null
+++ b/example/discovery/lib/main.dart
@@ -0,0 +1,180 @@
+// 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.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:flutter/material.dart';
+import 'package:v23discovery/discovery.dart' as discovery;
+import 'package:flutter/services.dart' show shell;
+import 'package:uuid/uuid.dart';
+
+const String _discoveryMojoUrl =
+    'https://discovery.mojo.v.io/discovery.mojo';
+
+void main() {
+  runApp(
+    new MaterialApp(
+      title: "Discovery Demo",
+      routes: <String, RouteBuilder>{
+        '/': (RouteArguments args) => new DiscoveryDemo()
+      }
+    )
+  );
+}
+
+final discovery.Client _discoveryClient =
+    new discovery.Client(shell.connectToService, _discoveryMojoUrl);
+
+class DiscoveryDemo extends StatefulComponent {
+  @override
+  State createState() => new DiscoveryDemoState();
+}
+
+final String instanceName = 'sample${new Uuid().v4()}';
+const String interfaceName = 'examples.discovery.sample';
+
+// The color of the icon to signify that the action is running.
+final highlightColor = Colors.blue[500];
+class DiscoveryDemoState extends State {
+  // The message to advertise.
+  // TODO: After v23proxy works for dart send this via rpc rather than
+  // attributes.
+  String message = 'Hello world!';
+  bool isAdvertising = false;
+  bool isScanning = false;
+  // The currently running advertisement.  This is used to stop the
+  // advertisement when we are done.
+  discovery.Advertiser adv = null;
+
+  // The currently running scanner.  This is used to stop the
+  // scanning when we are done.
+  discovery.Scanner scanner = null;
+  // A SplayTreeMap is used so the iteration order is stable.
+  Map<String, String> foundMessages = new SplayTreeMap();
+
+  Future _startAdvertising() async {
+    Map<String, String> attrs = new Map();
+    attrs['message'] = message;
+    var service = new discovery.Service()
+      ..attrs = attrs
+      ..interfaceName = interfaceName
+      ..instanceName = instanceName
+      // TODO put in a real endpoint here.  An empty array results in an
+      // advertisement error.
+      ..addrs = ['localhost:4000'];
+    adv = await _discoveryClient.advertise(service);
+    setState(() { isAdvertising = true;});
+  }
+
+  Future _stopAdvertising() async {
+    await adv?.stop();
+    adv = null;
+    setState(() { isAdvertising = false; });
+  }
+
+  void _toggleAdvertising() {
+    if (!isAdvertising) {
+      _startAdvertising();
+    } else {
+      _stopAdvertising();
+    }
+  }
+
+  Future _maybeUpdateAdv() async {
+    if (!isAdvertising) {
+      return;
+    }
+    // Don't call _stopAdvertising so the button's background color doesn't
+    // change briefly while we update the advertisement.
+    await adv.stop();
+    await _startAdvertising();
+  }
+
+  Future _startScanning() async {
+    var query = 'v.InterfaceName = "${interfaceName}"';
+    scanner = await _discoveryClient.scan(query);
+    scanner.onUpdate.listen((update) {
+      // Ignore advertisements for this device.
+      if (update.service.instanceName == instanceName) {
+        return;
+      }
+      setState(() {
+        var instanceName = update.service.instanceName;
+        if (update.updateType == discovery.UpdateType.found) {
+          foundMessages[instanceName] = update.service.attrs['message'];
+        } else {
+          foundMessages.remove(instanceName);
+        }
+      });
+    });
+    setState(() { isScanning = true;});
+  }
+
+  Future _stopScanning() async {
+    await scanner.stop();
+    scanner = null;
+    setState(() { isScanning = false; });
+  }
+
+  void _toggleScanning() {
+    if (!isScanning) {
+      _startScanning();
+    } else {
+      _stopScanning();
+    }
+  }
+
+  // Builds the row that contains the message input and the buttons to start
+  // and stop discovery.
+  Widget _buildButtonBar() {
+    var advColor;
+    if (isAdvertising) {
+      advColor = highlightColor;
+    }
+    var advButton = new IconButton(
+      icon: 'notification/tap_and_play',
+      color: advColor,
+      onPressed: _toggleAdvertising
+    );
+
+
+    var scanColor;
+    if (isScanning) {
+      scanColor = highlightColor;
+    }
+    var scanButton = new IconButton(
+      icon: 'action/search',
+      color: scanColor,
+      onPressed: _toggleScanning
+    );
+
+    var input = new Input(
+      initialValue: message,
+      onChanged: (String value) {
+        message = value;
+        _maybeUpdateAdv();
+      });
+    return new Row(
+      children: [
+        new Text('Message'),
+         new Flexible(child: input), advButton, scanButton]);
+  }
+
+  Widget build(BuildContext context) {
+    List<Widget> children = [_buildButtonBar()];
+    foundMessages.forEach((k, v) {
+      children.add(new Text('${k}: ${foundMessages[k]}', key:new Key(k)));
+    });
+
+    return new Scaffold(
+      toolBar: new ToolBar(
+        center: new Text("Discovery Demo")
+      ),
+      body: new Material(
+        child: new Column(children: children)
+      )
+    );
+  }
+}
diff --git a/example/discovery/pubspec.yaml b/example/discovery/pubspec.yaml
new file mode 100644
index 0000000..09f014c
--- /dev/null
+++ b/example/discovery/pubspec.yaml
@@ -0,0 +1,10 @@
+name: discovery
+description: A minimal Flutter project.
+dependencies:
+  flutter:
+    path: ../../../../../../flutter/packages/flutter
+  v23discovery: ">=0.0.12 <0.1.0"
+  uuid: any
+dev_dependencies:
+  flutter_tools:
+    path: "../../../../../flutter/packages/flutter_tools"
diff --git a/java/generated-src/mojom/vanadium/discovery.mojom.srcjar b/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
deleted file mode 100644
index ce1cada..0000000
--- a/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
+++ /dev/null
Binary files differ