From f25fe1fe6a16ddc6cd80e7125100f41f6bedb890 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Thu, 4 Apr 2019 15:35:45 -0700 Subject: [PATCH] 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 --- examples/android/strictmode/README.md | 8 + examples/android/strictmode/app/build.gradle | 55 ++++++ .../android/strictmode/app/proguard-rules.pro | 17 ++ .../app/src/main/AndroidManifest.xml | 22 +++ .../StrictModeHelloworldActivity.java | 183 ++++++++++++++++++ .../app/src/main/proto/helloworld.proto | 37 ++++ .../layout/activity_strictmodehelloworld.xml | 55 ++++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../app/src/main/res/values/strings.xml | 3 + examples/android/strictmode/build.gradle | 23 +++ examples/android/strictmode/demo.png | Bin 0 -> 47411 bytes examples/android/strictmode/settings.gradle | 1 + 15 files changed, 404 insertions(+) create mode 100644 examples/android/strictmode/README.md create mode 100644 examples/android/strictmode/app/build.gradle create mode 100644 examples/android/strictmode/app/proguard-rules.pro create mode 100644 examples/android/strictmode/app/src/main/AndroidManifest.xml create mode 100644 examples/android/strictmode/app/src/main/java/io/grpc/strictmodehelloworldexample/StrictModeHelloworldActivity.java create mode 100644 examples/android/strictmode/app/src/main/proto/helloworld.proto create mode 100644 examples/android/strictmode/app/src/main/res/layout/activity_strictmodehelloworld.xml create mode 100644 examples/android/strictmode/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/android/strictmode/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/android/strictmode/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/android/strictmode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/android/strictmode/app/src/main/res/values/strings.xml create mode 100644 examples/android/strictmode/build.gradle create mode 100644 examples/android/strictmode/demo.png create mode 100644 examples/android/strictmode/settings.gradle diff --git a/examples/android/strictmode/README.md b/examples/android/strictmode/README.md new file mode 100644 index 0000000000..1ed2aa6a2f --- /dev/null +++ b/examples/android/strictmode/README.md @@ -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) diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle new file mode 100644 index 0000000000..cfac883a54 --- /dev/null +++ b/examples/android/strictmode/app/build.gradle @@ -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' +} diff --git a/examples/android/strictmode/app/proguard-rules.pro b/examples/android/strictmode/app/proguard-rules.pro new file mode 100644 index 0000000000..1507a52678 --- /dev/null +++ b/examples/android/strictmode/app/proguard-rules.pro @@ -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 diff --git a/examples/android/strictmode/app/src/main/AndroidManifest.xml b/examples/android/strictmode/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..41c63f0554 --- /dev/null +++ b/examples/android/strictmode/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/examples/android/strictmode/app/src/main/java/io/grpc/strictmodehelloworldexample/StrictModeHelloworldActivity.java b/examples/android/strictmode/app/src/main/java/io/grpc/strictmodehelloworldexample/StrictModeHelloworldActivity.java new file mode 100644 index 0000000000..44e37cc0ea --- /dev/null +++ b/examples/android/strictmode/app/src/main/java/io/grpc/strictmodehelloworldexample/StrictModeHelloworldActivity.java @@ -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 { + private final WeakReference activityReference; + private ManagedChannel channel; + + private GrpcTask(Activity activity) { + this.activityReference = new WeakReference(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(), + 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); + } + } +} diff --git a/examples/android/strictmode/app/src/main/proto/helloworld.proto b/examples/android/strictmode/app/src/main/proto/helloworld.proto new file mode 100644 index 0000000000..c60d9416f1 --- /dev/null +++ b/examples/android/strictmode/app/src/main/proto/helloworld.proto @@ -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; +} diff --git a/examples/android/strictmode/app/src/main/res/layout/activity_strictmodehelloworld.xml b/examples/android/strictmode/app/src/main/res/layout/activity_strictmodehelloworld.xml new file mode 100644 index 0000000000..18b05c3bd6 --- /dev/null +++ b/examples/android/strictmode/app/src/main/res/layout/activity_strictmodehelloworld.xml @@ -0,0 +1,55 @@ + + + + + + + + + + +