examples/android: add example for grpc running under StrictMode (#5527)

* added android example running under strictmode to detect grpc clear text network transport

* renamed andriod strictmode example

* use OkHttpChannelBuilder's api to feed with user's own transport executor

* bump gradle and proto-plugin version to match that in master

* add README with a simple demo image

* Update README to use relative link to helloworld
This commit is contained in:
Chengyuan Zhang 2019-04-04 15:35:45 -07:00 committed by GitHub
parent 0db0c637a1
commit f25fe1fe6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 404 additions and 0 deletions

View File

@ -0,0 +1,8 @@
gRPC Android StrictMode Example
========================
- This example intends to show the compatibility of gRPC with Android StrictMode.
- Android SDK version 28 is required for [`StrictMode.VmPolicy.Builder.penaltyListener`](https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html#penaltyListener(java.util.concurrent.Executor,%20android.os.StrictMode.OnVmViolationListener)) used in the example.
- This example does the same thing as [HelloWorld example](../helloworld) except popping up a dialog for detected StrictMode policy violation (shown below).
![demo img](./demo.png)

View File

@ -0,0 +1,55 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
android {
compileSdkVersion 28
defaultConfig {
applicationId "io.grpc.strictmodehelloworldexample"
// API level 28 is required for StrictMode penaltyListener
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
debug { minifyEnabled false }
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage'
textReport true
textOutput "stdout"
}
}
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.7.0' }
plugins {
javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.21.0-SNAPSHOT' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
all().each { task ->
task.plugins {
javalite {}
grpc { // Options added to --grpc_out
option 'lite' }
}
}
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
// You need to build grpc-java to obtain these libraries below.
implementation 'io.grpc:grpc-okhttp:1.21.0-SNAPSHOT' // CURRENT_GRPC_VERSION
implementation 'io.grpc:grpc-protobuf-lite:1.21.0-SNAPSHOT' // CURRENT_GRPC_VERSION
implementation 'io.grpc:grpc-stub:1.21.0-SNAPSHOT' // CURRENT_GRPC_VERSION
implementation 'javax.annotation:javax.annotation-api:1.2'
}

View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in $ANDROID_HOME/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-dontwarn com.google.common.**
# Ignores: can't find referenced class javax.lang.model.element.Modifier
-dontwarn com.google.errorprone.annotations.**
-dontwarn javax.naming.**
-dontwarn okio.**
-dontwarn sun.misc.Unsafe

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.grpc.strictmodehelloworldexample" >
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Base.V7.Theme.AppCompat.Light" >
<activity
android:name=".StrictModeHelloworldActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,183 @@
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.strictmodehelloworldexample;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.net.TrafficStats;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.os.StrictMode.OnVmViolationListener;
import android.os.StrictMode.VmPolicy;
import android.os.strictmode.Violation;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.ManagedChannel;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.okhttp.OkHttpChannelBuilder;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class StrictModeHelloworldActivity extends AppCompatActivity {
private Button sendButton;
private EditText hostEdit;
private EditText portEdit;
private EditText messageEdit;
private TextView resultText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
VmPolicy policy =
new StrictMode.VmPolicy.Builder()
.detectCleartextNetwork()
.penaltyListener(
MoreExecutors.directExecutor(),
new OnVmViolationListener() {
@Override
public void onVmViolation(final Violation v) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(StrictModeHelloworldActivity.this)
.setTitle("StrictMode VM Violation")
.setMessage(v.getLocalizedMessage())
.show();
}
});
}
})
.penaltyLog()
.build();
StrictMode.setVmPolicy(policy);
setContentView(R.layout.activity_strictmodehelloworld);
sendButton = (Button) findViewById(R.id.send_button);
hostEdit = (EditText) findViewById(R.id.host_edit_text);
portEdit = (EditText) findViewById(R.id.port_edit_text);
messageEdit = (EditText) findViewById(R.id.message_edit_text);
resultText = (TextView) findViewById(R.id.grpc_response_text);
resultText.setMovementMethod(new ScrollingMovementMethod());
}
public void sendMessage(View view) {
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(hostEdit.getWindowToken(), 0);
sendButton.setEnabled(false);
resultText.setText("");
new GrpcTask(this)
.execute(
hostEdit.getText().toString(),
messageEdit.getText().toString(),
portEdit.getText().toString());
}
private static class GrpcTask extends AsyncTask<String, Void, String> {
private final WeakReference<Activity> activityReference;
private ManagedChannel channel;
private GrpcTask(Activity activity) {
this.activityReference = new WeakReference<Activity>(activity);
}
@Override
protected String doInBackground(String... params) {
String host = params[0];
String message = params[1];
String portStr = params[2];
int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
try {
channel =
OkHttpChannelBuilder.forAddress(host, port)
.transportExecutor(new NetworkTaggingExecutor(0xFDD))
.usePlaintext()
.build();
GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
HelloRequest request = HelloRequest.newBuilder().setName(message).build();
HelloReply reply = stub.sayHello(request);
return reply.getMessage();
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.flush();
return String.format("Failed... : %n%s", sw);
}
}
@Override
protected void onPostExecute(String result) {
try {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Activity activity = activityReference.get();
if (activity == null) {
return;
}
TextView resultText = (TextView) activity.findViewById(R.id.grpc_response_text);
Button sendButton = (Button) activity.findViewById(R.id.send_button);
resultText.setText(result);
sendButton.setEnabled(true);
}
}
private static class NetworkTaggingExecutor extends ThreadPoolExecutor {
private int trafficStatsTag;
NetworkTaggingExecutor(int tag) {
super(
0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder().setDaemon(true).setNameFormat("grpc-android-%d").build());
trafficStatsTag = tag;
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
TrafficStats.setThreadStatsTag(trafficStatsTag);
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
TrafficStats.clearThreadStatsTag();
super.afterExecute(r, t);
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright 2015 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

View File

@ -0,0 +1,55 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/host_edit_text"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Enter Host" />
<EditText
android:id="@+id/port_edit_text"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="Enter Port" />
</LinearLayout>
<EditText
android:id="@+id/message_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter message to send" />
<Button
android:id="@+id/send_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="sendMessage"
android:text="Send Grpc Request" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textSize="16sp"
android:text="Response:" />
<TextView
android:id="@+id/grpc_response_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars = "vertical"
android:textSize="16sp" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">GrpcStrictModeHelloworldExample</string>
</resources>

View File

@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.8"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
mavenLocal()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1 @@
include ':app'