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
diff --git a/examples/android/strictmode/README.md b/examples/android/strictmode/README.md
new file mode 100644
index 0000000..1ed2aa6
--- /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 0000000..cfac883
--- /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 0000000..1507a52
--- /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 0000000..41c63f0
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/AndroidManifest.xml
@@ -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>
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 0000000..44e37cc
--- /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<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);
+    }
+  }
+}
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 0000000..c60d941
--- /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 0000000..18b05c3
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/layout/activity_strictmodehelloworld.xml
@@ -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>
diff --git a/examples/android/strictmode/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/android/strictmode/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/strictmode/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/android/strictmode/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/strictmode/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/android/strictmode/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/strictmode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/android/strictmode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/strictmode/app/src/main/res/values/strings.xml b/examples/android/strictmode/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..53389a0
--- /dev/null
+++ b/examples/android/strictmode/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">GrpcStrictModeHelloworldExample</string>
+</resources>
diff --git a/examples/android/strictmode/build.gradle b/examples/android/strictmode/build.gradle
new file mode 100644
index 0000000..4e934ef
--- /dev/null
+++ b/examples/android/strictmode/build.gradle
@@ -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()
+    }
+}
diff --git a/examples/android/strictmode/demo.png b/examples/android/strictmode/demo.png
new file mode 100644
index 0000000..dd409cf
--- /dev/null
+++ b/examples/android/strictmode/demo.png
Binary files differ
diff --git a/examples/android/strictmode/settings.gradle b/examples/android/strictmode/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/android/strictmode/settings.gradle
@@ -0,0 +1 @@
+include ':app'