1 /*
2  * Copyright 2016 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.routeguideexample;
18 
19 import android.content.Context;
20 import android.os.AsyncTask;
21 import android.os.Bundle;
22 import android.support.v7.app.AppCompatActivity;
23 import android.text.TextUtils;
24 import android.text.method.ScrollingMovementMethod;
25 import android.view.View;
26 import android.view.inputmethod.InputMethodManager;
27 import android.widget.Button;
28 import android.widget.EditText;
29 import android.widget.TextView;
30 import io.grpc.ManagedChannel;
31 import io.grpc.ManagedChannelBuilder;
32 import io.grpc.StatusRuntimeException;
33 import io.grpc.routeguideexample.RouteGuideGrpc.RouteGuideBlockingStub;
34 import io.grpc.routeguideexample.RouteGuideGrpc.RouteGuideStub;
35 import io.grpc.stub.StreamObserver;
36 import java.io.PrintWriter;
37 import java.io.StringWriter;
38 import java.lang.ref.WeakReference;
39 import java.text.MessageFormat;
40 import java.util.ArrayList;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Random;
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.TimeUnit;
46 
47 public class RouteGuideActivity extends AppCompatActivity {
48   private EditText hostEdit;
49   private EditText portEdit;
50   private Button startRouteGuideButton;
51   private Button exitRouteGuideButton;
52   private Button getFeatureButton;
53   private Button listFeaturesButton;
54   private Button recordRouteButton;
55   private Button routeChatButton;
56   private TextView resultText;
57   private ManagedChannel channel;
58 
59   @Override
onCreate(Bundle savedInstanceState)60   protected void onCreate(Bundle savedInstanceState) {
61     super.onCreate(savedInstanceState);
62     setContentView(R.layout.activity_routeguide);
63     hostEdit = (EditText) findViewById(R.id.host_edit_text);
64     portEdit = (EditText) findViewById(R.id.port_edit_text);
65     startRouteGuideButton = (Button) findViewById(R.id.start_route_guide_button);
66     exitRouteGuideButton = (Button) findViewById(R.id.exit_route_guide_button);
67     getFeatureButton = (Button) findViewById(R.id.get_feature_button);
68     listFeaturesButton = (Button) findViewById(R.id.list_features_button);
69     recordRouteButton = (Button) findViewById(R.id.record_route_button);
70     routeChatButton = (Button) findViewById(R.id.route_chat_button);
71     resultText = (TextView) findViewById(R.id.result_text);
72     resultText.setMovementMethod(new ScrollingMovementMethod());
73     disableButtons();
74   }
75 
startRouteGuide(View view)76   public void startRouteGuide(View view) {
77     String host = hostEdit.getText().toString();
78     String portStr = portEdit.getText().toString();
79     int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
80     ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
81         .hideSoftInputFromWindow(hostEdit.getWindowToken(), 0);
82     channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
83     hostEdit.setEnabled(false);
84     portEdit.setEnabled(false);
85     startRouteGuideButton.setEnabled(false);
86     enableButtons();
87   }
88 
exitRouteGuide(View view)89   public void exitRouteGuide(View view) {
90     channel.shutdown();
91     disableButtons();
92     hostEdit.setEnabled(true);
93     portEdit.setEnabled(true);
94     startRouteGuideButton.setEnabled(true);
95   }
96 
getFeature(View view)97   public void getFeature(View view) {
98     setResultText("");
99     disableButtons();
100     new GrpcTask(new GetFeatureRunnable(), channel, this).execute();
101   }
102 
listFeatures(View view)103   public void listFeatures(View view) {
104     setResultText("");
105     disableButtons();
106     new GrpcTask(new ListFeaturesRunnable(), channel, this).execute();
107   }
108 
recordRoute(View view)109   public void recordRoute(View view) {
110     setResultText("");
111     disableButtons();
112     new GrpcTask(new RecordRouteRunnable(), channel, this).execute();
113   }
114 
routeChat(View view)115   public void routeChat(View view) {
116     setResultText("");
117     disableButtons();
118     new GrpcTask(new RouteChatRunnable(), channel, this).execute();
119   }
120 
setResultText(String text)121   private void setResultText(String text) {
122     resultText.setText(text);
123   }
124 
disableButtons()125   private void disableButtons() {
126     getFeatureButton.setEnabled(false);
127     listFeaturesButton.setEnabled(false);
128     recordRouteButton.setEnabled(false);
129     routeChatButton.setEnabled(false);
130     exitRouteGuideButton.setEnabled(false);
131   }
132 
enableButtons()133   private void enableButtons() {
134     exitRouteGuideButton.setEnabled(true);
135     getFeatureButton.setEnabled(true);
136     listFeaturesButton.setEnabled(true);
137     recordRouteButton.setEnabled(true);
138     routeChatButton.setEnabled(true);
139   }
140 
141   private static class GrpcTask extends AsyncTask<Void, Void, String> {
142     private final GrpcRunnable grpcRunnable;
143     private final ManagedChannel channel;
144     private final WeakReference<RouteGuideActivity> activityReference;
145 
GrpcTask(GrpcRunnable grpcRunnable, ManagedChannel channel, RouteGuideActivity activity)146     GrpcTask(GrpcRunnable grpcRunnable, ManagedChannel channel, RouteGuideActivity activity) {
147       this.grpcRunnable = grpcRunnable;
148       this.channel = channel;
149       this.activityReference = new WeakReference<RouteGuideActivity>(activity);
150     }
151 
152     @Override
doInBackground(Void... nothing)153     protected String doInBackground(Void... nothing) {
154       try {
155         String logs =
156             grpcRunnable.run(
157                 RouteGuideGrpc.newBlockingStub(channel), RouteGuideGrpc.newStub(channel));
158         return "Success!\n" + logs;
159       } catch (Exception e) {
160         StringWriter sw = new StringWriter();
161         PrintWriter pw = new PrintWriter(sw);
162         e.printStackTrace(pw);
163         pw.flush();
164         return "Failed... :\n" + sw;
165       }
166     }
167 
168     @Override
onPostExecute(String result)169     protected void onPostExecute(String result) {
170       RouteGuideActivity activity = activityReference.get();
171       if (activity == null) {
172         return;
173       }
174       activity.setResultText(result);
175       activity.enableButtons();
176     }
177   }
178 
179   private interface GrpcRunnable {
180     /** Perform a grpcRunnable and return all the logs. */
run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)181     String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub) throws Exception;
182   }
183 
184   private static class GetFeatureRunnable implements GrpcRunnable {
185     @Override
run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)186     public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
187         throws Exception {
188       return getFeature(409146138, -746188906, blockingStub);
189     }
190 
191     /** Blocking unary call example. Calls getFeature and prints the response. */
getFeature(int lat, int lon, RouteGuideBlockingStub blockingStub)192     private String getFeature(int lat, int lon, RouteGuideBlockingStub blockingStub)
193         throws StatusRuntimeException {
194       StringBuffer logs = new StringBuffer();
195       appendLogs(logs, "*** GetFeature: lat={0} lon={1}", lat, lon);
196 
197       Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
198 
199       Feature feature;
200       feature = blockingStub.getFeature(request);
201       if (RouteGuideUtil.exists(feature)) {
202         appendLogs(
203             logs,
204             "Found feature called \"{0}\" at {1}, {2}",
205             feature.getName(),
206             RouteGuideUtil.getLatitude(feature.getLocation()),
207             RouteGuideUtil.getLongitude(feature.getLocation()));
208       } else {
209         appendLogs(
210             logs,
211             "Found no feature at {0}, {1}",
212             RouteGuideUtil.getLatitude(feature.getLocation()),
213             RouteGuideUtil.getLongitude(feature.getLocation()));
214       }
215       return logs.toString();
216     }
217   }
218 
219   private static class ListFeaturesRunnable implements GrpcRunnable {
220     @Override
run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)221     public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
222         throws Exception {
223       return listFeatures(400000000, -750000000, 420000000, -730000000, blockingStub);
224     }
225 
226     /**
227      * Blocking server-streaming example. Calls listFeatures with a rectangle of interest. Prints
228      * each response feature as it arrives.
229      */
listFeatures( int lowLat, int lowLon, int hiLat, int hiLon, RouteGuideBlockingStub blockingStub)230     private String listFeatures(
231         int lowLat, int lowLon, int hiLat, int hiLon, RouteGuideBlockingStub blockingStub)
232         throws StatusRuntimeException {
233       StringBuffer logs = new StringBuffer("Result: ");
234       appendLogs(
235           logs,
236           "*** ListFeatures: lowLat={0} lowLon={1} hiLat={2} hiLon={3}",
237           lowLat,
238           lowLon,
239           hiLat,
240           hiLon);
241 
242       Rectangle request =
243           Rectangle.newBuilder()
244               .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
245               .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build())
246               .build();
247       Iterator<Feature> features;
248       features = blockingStub.listFeatures(request);
249 
250       while (features.hasNext()) {
251         Feature feature = features.next();
252         appendLogs(logs, feature.toString());
253       }
254       return logs.toString();
255     }
256   }
257 
258   private static class RecordRouteRunnable implements GrpcRunnable {
259     private Throwable failed;
260 
261     @Override
run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)262     public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
263         throws Exception {
264       List<Point> points = new ArrayList<>();
265       points.add(Point.newBuilder().setLatitude(407838351).setLongitude(-746143763).build());
266       points.add(Point.newBuilder().setLatitude(408122808).setLongitude(-743999179).build());
267       points.add(Point.newBuilder().setLatitude(413628156).setLongitude(-749015468).build());
268       return recordRoute(points, 5, asyncStub);
269     }
270 
271     /**
272      * Async client-streaming example. Sends {@code numPoints} randomly chosen points from {@code
273      * features} with a variable delay in between. Prints the statistics when they are sent from the
274      * server.
275      */
recordRoute(List<Point> points, int numPoints, RouteGuideStub asyncStub)276     private String recordRoute(List<Point> points, int numPoints, RouteGuideStub asyncStub)
277         throws InterruptedException, RuntimeException {
278       final StringBuffer logs = new StringBuffer();
279       appendLogs(logs, "*** RecordRoute");
280 
281       final CountDownLatch finishLatch = new CountDownLatch(1);
282       StreamObserver<RouteSummary> responseObserver =
283           new StreamObserver<RouteSummary>() {
284             @Override
285             public void onNext(RouteSummary summary) {
286               appendLogs(
287                   logs,
288                   "Finished trip with {0} points. Passed {1} features. "
289                       + "Travelled {2} meters. It took {3} seconds.",
290                   summary.getPointCount(),
291                   summary.getFeatureCount(),
292                   summary.getDistance(),
293                   summary.getElapsedTime());
294             }
295 
296             @Override
297             public void onError(Throwable t) {
298               failed = t;
299               finishLatch.countDown();
300             }
301 
302             @Override
303             public void onCompleted() {
304               appendLogs(logs, "Finished RecordRoute");
305               finishLatch.countDown();
306             }
307           };
308 
309       StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
310       try {
311         // Send numPoints points randomly selected from the points list.
312         Random rand = new Random();
313         for (int i = 0; i < numPoints; ++i) {
314           int index = rand.nextInt(points.size());
315           Point point = points.get(index);
316           appendLogs(
317               logs,
318               "Visiting point {0}, {1}",
319               RouteGuideUtil.getLatitude(point),
320               RouteGuideUtil.getLongitude(point));
321           requestObserver.onNext(point);
322           // Sleep for a bit before sending the next one.
323           Thread.sleep(rand.nextInt(1000) + 500);
324           if (finishLatch.getCount() == 0) {
325             // RPC completed or errored before we finished sending.
326             // Sending further requests won't error, but they will just be thrown away.
327             break;
328           }
329         }
330       } catch (RuntimeException e) {
331         // Cancel RPC
332         requestObserver.onError(e);
333         throw e;
334       }
335       // Mark the end of requests
336       requestObserver.onCompleted();
337 
338       // Receiving happens asynchronously
339       if (!finishLatch.await(1, TimeUnit.MINUTES)) {
340         throw new RuntimeException(
341             "Could not finish rpc within 1 minute, the server is likely down");
342       }
343 
344       if (failed != null) {
345         throw new RuntimeException(failed);
346       }
347       return logs.toString();
348     }
349   }
350 
351   private static class RouteChatRunnable implements GrpcRunnable {
352     private Throwable failed;
353 
354     @Override
run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)355     public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
356         throws Exception {
357       return routeChat(asyncStub);
358     }
359 
360     /**
361      * Bi-directional example, which can only be asynchronous. Send some chat messages, and print
362      * any chat messages that are sent from the server.
363      */
routeChat(RouteGuideStub asyncStub)364     private String routeChat(RouteGuideStub asyncStub)
365         throws InterruptedException, RuntimeException {
366       final StringBuffer logs = new StringBuffer();
367       appendLogs(logs, "*** RouteChat");
368       final CountDownLatch finishLatch = new CountDownLatch(1);
369       StreamObserver<RouteNote> requestObserver =
370           asyncStub.routeChat(
371               new StreamObserver<RouteNote>() {
372                 @Override
373                 public void onNext(RouteNote note) {
374                   appendLogs(
375                       logs,
376                       "Got message \"{0}\" at {1}, {2}",
377                       note.getMessage(),
378                       note.getLocation().getLatitude(),
379                       note.getLocation().getLongitude());
380                 }
381 
382                 @Override
383                 public void onError(Throwable t) {
384                   failed = t;
385                   finishLatch.countDown();
386                 }
387 
388                 @Override
389                 public void onCompleted() {
390                   appendLogs(logs, "Finished RouteChat");
391                   finishLatch.countDown();
392                 }
393               });
394 
395       try {
396         RouteNote[] requests = {
397           newNote("First message", 0, 0),
398           newNote("Second message", 0, 1),
399           newNote("Third message", 1, 0),
400           newNote("Fourth message", 1, 1)
401         };
402 
403         for (RouteNote request : requests) {
404           appendLogs(
405               logs,
406               "Sending message \"{0}\" at {1}, {2}",
407               request.getMessage(),
408               request.getLocation().getLatitude(),
409               request.getLocation().getLongitude());
410           requestObserver.onNext(request);
411         }
412       } catch (RuntimeException e) {
413         // Cancel RPC
414         requestObserver.onError(e);
415         throw e;
416       }
417       // Mark the end of requests
418       requestObserver.onCompleted();
419 
420       // Receiving happens asynchronously
421       if (!finishLatch.await(1, TimeUnit.MINUTES)) {
422         throw new RuntimeException(
423             "Could not finish rpc within 1 minute, the server is likely down");
424       }
425 
426       if (failed != null) {
427         throw new RuntimeException(failed);
428       }
429 
430       return logs.toString();
431     }
432   }
433 
appendLogs(StringBuffer logs, String msg, Object... params)434   private static void appendLogs(StringBuffer logs, String msg, Object... params) {
435     if (params.length > 0) {
436       logs.append(MessageFormat.format(msg, params));
437     } else {
438       logs.append(msg);
439     }
440     logs.append("\n");
441   }
442 
newNote(String message, int lat, int lon)443   private static RouteNote newNote(String message, int lat, int lon) {
444     return RouteNote.newBuilder()
445         .setMessage(message)
446         .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build())
447         .build();
448   }
449 }
450