Initial commit of our new Android app to demo Skia.

Currently, there's only a simple Activity with a title bar and a surface view
that connects with Skia viewer native application.

Before integrating user action events, I want to make sure that the design and
implementation so far are good.  Note that the old NativeApplication-based
Activity (ViewerActivity) is still there for reference and test purposes. It
will be removed eventually.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1952323004

Review-Url: https://codereview.chromium.org/1952323004
diff --git a/platform_tools/android/apps/viewer/src/main/AndroidManifest.xml b/platform_tools/android/apps/viewer/src/main/AndroidManifest.xml
index 2ed88e1..a1fd971 100644
--- a/platform_tools/android/apps/viewer/src/main/AndroidManifest.xml
+++ b/platform_tools/android/apps/viewer/src/main/AndroidManifest.xml
@@ -7,14 +7,11 @@
 
   <application
       android:allowBackup="false"
+      android:theme="@android:style/Theme.Holo.Light"
+      android:name=".ViewerApplication"
       android:label="Viewer">
 
-    <activity android:name=".ViewerActivity"
-              android:label="Viewer"
-              android:screenOrientation="portrait"
-              android:configChanges="orientation|keyboardHidden">
-      <meta-data android:name="android.app.lib_name"
-                 android:value="viewer" />
+    <activity android:name=".ViewerActivity">
       <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
diff --git a/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerActivity.java b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerActivity.java
index 950d6b2..48cec39 100644
--- a/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerActivity.java
+++ b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerActivity.java
@@ -7,22 +7,48 @@
 
 package org.skia.viewer;
 
-import android.app.ActionBar;
+import android.app.Activity;
 import android.os.Bundle;
-import android.provider.Settings;
-import android.view.View;
-import android.view.WindowManager;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 
-public class ViewerActivity extends android.app.NativeActivity {
-    static {
-        System.loadLibrary("skia_android");
-    }
-    
+public class ViewerActivity extends Activity implements SurfaceHolder.Callback {
+    private SurfaceView mView;
+    private ViewerApplication mApplication;
+
+    private native void onSurfaceCreated(long handle, Surface surface);
+    private native void onSurfaceChanged(long handle, Surface surface);
+    private native void onSurfaceDestroyed(long handle);
+
     @Override
-    public void onCreate(Bundle savedInstanceState) 
-    {
+    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        ActionBar ab = this.getActionBar();
-        ab.hide();
+        setContentView(R.layout.activity_main);
+
+        mApplication = (ViewerApplication) getApplication();
+        mView = (SurfaceView) findViewById(R.id.surfaceView);
+        mView.getHolder().addCallback(this);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        if (mApplication.getNativeHandle() != 0) {
+            onSurfaceCreated(mApplication.getNativeHandle(), holder.getSurface());
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        if (mApplication.getNativeHandle() != 0) {
+            onSurfaceChanged(mApplication.getNativeHandle(), holder.getSurface());
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        if (mApplication.getNativeHandle() != 0) {
+            onSurfaceDestroyed(mApplication.getNativeHandle());
+        }
     }
 }
diff --git a/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java
new file mode 100644
index 0000000..9389f72
--- /dev/null
+++ b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+package org.skia.viewer;
+
+import android.app.Application;
+
+public class ViewerApplication extends Application {
+    private long mNativeHandle = 0;
+
+    static {
+        System.loadLibrary("skia_android");
+        System.loadLibrary("viewer");
+    }
+
+    private native long createNativeApp();
+    private native void destroyNativeApp(long handle);
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mNativeHandle = createNativeApp();
+    }
+
+    @Override
+    public void onTerminate() {
+        if (mNativeHandle != 0) {
+            destroyNativeApp(mNativeHandle);
+            mNativeHandle = 0;
+        }
+        super.onTerminate();
+    }
+
+    public long getNativeHandle() {
+        return mNativeHandle;
+    }
+}
diff --git a/platform_tools/android/apps/viewer/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/viewer/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6597a48
--- /dev/null
+++ b/platform_tools/android/apps/viewer/src/main/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/mainLayout"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ViewerActivity">
+
+    <SurfaceView
+        android:id="@+id/surfaceView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_centerVertical="true"
+        android:layout_centerHorizontal="true" />
+
+</LinearLayout>
diff --git a/tools/viewer/sk_app/VulkanWindowContext.h b/tools/viewer/sk_app/VulkanWindowContext.h
index e186881..ec80a32 100644
--- a/tools/viewer/sk_app/VulkanWindowContext.h
+++ b/tools/viewer/sk_app/VulkanWindowContext.h
@@ -46,8 +46,8 @@
         this->createSwapchain(w, h, fDisplayParams);
     }
 
-    const DisplayParams& getDisplayParams() { return fDisplayParams; }
-    void setDisplayParams(const DisplayParams& params) {
+    const DisplayParams& getDisplayParams() override { return fDisplayParams; }
+    void setDisplayParams(const DisplayParams& params) override {
         this->createSwapchain(fWidth, fHeight, params);
     }
 
diff --git a/tools/viewer/sk_app/android/Window_android.cpp b/tools/viewer/sk_app/android/Window_android.cpp
index 9def29f..09e7ef1 100644
--- a/tools/viewer/sk_app/android/Window_android.cpp
+++ b/tools/viewer/sk_app/android/Window_android.cpp
@@ -13,22 +13,17 @@
 
 Window* Window::CreateNativeWindow(void* platformData) {
     Window_android* window = new Window_android();
-    if (!window->init((android_app*)platformData)) {
+    if (!window->init((SkiaAndroidApp*)platformData)) {
         delete window;
         return nullptr;
     }
     return window;
 }
 
-static void handle_cmd(struct android_app* app, int32_t cmd);
-static int32_t handle_input(struct android_app* app, AInputEvent* event);
-
-bool Window_android::init(android_app* app) {
-    SkASSERT(app);
-    mApp = app;
-    mApp->userData = this;
-    mApp->onAppCmd = handle_cmd;
-    mApp->onInputEvent = handle_input;
+bool Window_android::init(SkiaAndroidApp* skiaAndroidApp) {
+    SkASSERT(skiaAndroidApp);
+    fSkiaAndroidApp = skiaAndroidApp;
+    fSkiaAndroidApp->fWindow = this;
     return true;
 }
 
@@ -53,131 +48,15 @@
     SkASSERT(window);
     ContextPlatformData_android platformData;
     platformData.fNativeWindow = window;
-    fWindowContext = VulkanWindowContext::Create((void*)&platformData, mSampleCount, fSRGB);
+    fWindowContext = VulkanWindowContext::Create((void*)&platformData, fDisplayParams);
+    fNativeWindowInitialized = true;
 }
 
-static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
-    if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
-        SkDebugf("Failure writing android_app cmd: %s\n", strerror(errno));
-    }
+void Window_android::onDisplayDestroyed() {
+    fNativeWindowInitialized = false;
+    detach();
 }
 
-void Window_android::inval() {
-    android_app_write_cmd(mApp, APP_CMD_INVAL_WINDOW);
-}
-
-void Window_android::paintIfNeeded() {
-    if (mApp->window || !mContentRect.isEmpty()) {
-        this->onPaint();
-    }
-}
-
-/**
- * Process the next main command.
- */
-static void handle_cmd(struct android_app* app, int32_t cmd) {
-    Window_android* window = (Window_android*)app->userData;
-    switch (cmd) {
-        case APP_CMD_INIT_WINDOW:
-            // The window is being shown, get it ready.
-            SkASSERT(app->window);
-            window->initDisplay(app->window);
-            window->paintIfNeeded();
-            break;
-        case APP_CMD_WINDOW_RESIZED: {
-            int width = ANativeWindow_getWidth(app->window);
-            int height = ANativeWindow_getHeight(app->window);
-            window->onResize(width, height);
-            break;
-        }
-        case APP_CMD_CONTENT_RECT_CHANGED:
-            window->setContentRect(app->contentRect.left, app->contentRect.top,
-                                   app->contentRect.right, app->contentRect.bottom);
-            window->paintIfNeeded();
-            break;
-        case APP_CMD_TERM_WINDOW:
-            // The window is being hidden or closed, clean it up.
-            window->detach();
-            break;
-        case APP_CMD_INVAL_WINDOW:
-            window->paintIfNeeded();
-            break;
-    }
-}
-
-static Window::Key get_key(int32_t keycode) {
-    static const struct {
-        int32_t     fAndroidKey;
-        Window::Key fWindowKey;
-    } gPair[] = {
-        { AKEYCODE_BACK, Window::kBack_Key },
-        { AKEYCODE_VOLUME_UP, Window::kLeft_Key },
-        { AKEYCODE_VOLUME_DOWN, Window::kRight_Key }
-    };
-    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
-        if (gPair[i].fAndroidKey == keycode) {
-            return gPair[i].fWindowKey;
-        }
-    }
-    return Window::kNONE_Key;
-}
-
-static Window::InputState get_action(int32_t action) {
-    static const struct {
-        int32_t            fAndroidAction;
-        Window::InputState fInputState;
-    } gPair[] = {
-        { AKEY_STATE_DOWN, Window::kDown_InputState },
-        { AKEY_STATE_UP, Window::kUp_InputState },
-    };
-    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
-        if (gPair[i].fAndroidAction == action) {
-            return gPair[i].fInputState;
-        }
-    }
-    return Window::kMove_InputState;
-}
-
-static int32_t get_key_modifiers(AInputEvent* event) {
-    static const struct {
-        int32_t fAndroidState;
-        int32_t fWindowModifier;
-    } gPair[] = {
-        { AMETA_SHIFT_ON, Window::kShift_ModifierKey },
-        { AMETA_CTRL_ON, Window::kControl_ModifierKey },
-    };
-
-    int32_t metaState = AKeyEvent_getMetaState(event);
-    int32_t modifiers = 0;
-
-    if (AKeyEvent_getRepeatCount(event) == 0) {
-        modifiers |= Window::kFirstPress_ModifierKey;
-    }
-
-    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
-        if (gPair[i].fAndroidState == metaState) {
-            modifiers |= gPair[i].fWindowModifier;
-        }
-    }
-    return modifiers;
-}
-
-/**
- * Process the next input event.
- */
-static int32_t handle_input(struct android_app* app, AInputEvent* event) {
-    Window_android* window = (Window_android*)app->userData;
-    switch(AInputEvent_getType(event)) {
-        case AINPUT_EVENT_TYPE_MOTION:
-            break;
-        case AINPUT_EVENT_TYPE_KEY:
-            Window::Key key = get_key(AKeyEvent_getKeyCode(event));
-            Window::InputState state = get_action(AKeyEvent_getAction(event));
-            int32_t mod = get_key_modifiers(event);
-            window->onKey(key, state, mod);
-            return true; // eat all key events
-    }
-    return 0;
-}
+void Window_android::inval() { fSkiaAndroidApp->postMessage(Message(kContentInvalidated)); }
 
 }   // namespace sk_app
diff --git a/tools/viewer/sk_app/android/Window_android.h b/tools/viewer/sk_app/android/Window_android.h
index b570615..45e5bbe 100644
--- a/tools/viewer/sk_app/android/Window_android.h
+++ b/tools/viewer/sk_app/android/Window_android.h
@@ -9,24 +9,18 @@
 #define Window_android_DEFINED
 
 #include "../Window.h"
-#include <android_native_app_glue.h>
+#include "surface_glue_android.h"
 
 namespace sk_app {
 
-enum {
-    /**
-     * Leave plenty of space between this item and the ones defined in the glue layer
-     */
-    APP_CMD_INVAL_WINDOW = 64,
-};
-
 class Window_android : public Window {
 public:
     Window_android() : Window() {}
     ~Window_android() override {}
 
-    bool init(android_app* app_state);
+    bool init(SkiaAndroidApp* skiaAndroidApp);
     void initDisplay(ANativeWindow* window);
+    void onDisplayDestroyed();
 
     void setTitle(const char*) override;
     void show() override {}
@@ -34,17 +28,16 @@
     bool attach(BackEndType attachType, const DisplayParams& params) override;
     void inval() override;
 
-    void paintIfNeeded();
-
     bool scaleContentToFit() const override { return true; }
     bool supportsContentRect() const override { return true; }
-    SkRect getContentRect() override { return mContentRect; }
-    void setContentRect(int l, int t, int r, int b) { mContentRect.set(l,t,r,b); }
+    SkRect getContentRect() override { return fContentRect; }
+    void setContentRect(int l, int t, int r, int b) { fContentRect.set(l,t,r,b); }
 
 private:
-    android_app* mApp = nullptr;
-    SkRect mContentRect;
+    SkiaAndroidApp* fSkiaAndroidApp = nullptr;
+    SkRect fContentRect;
     DisplayParams fDisplayParams;
+    bool fNativeWindowInitialized = false;
 };
 
 }   // namespace sk_app
diff --git a/tools/viewer/sk_app/android/surface_glue_android.cpp b/tools/viewer/sk_app/android/surface_glue_android.cpp
new file mode 100644
index 0000000..b1d0029
--- /dev/null
+++ b/tools/viewer/sk_app/android/surface_glue_android.cpp
@@ -0,0 +1,174 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "surface_glue_android.h"
+
+#include <jni.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <android/looper.h>
+#include <android/native_window_jni.h>
+
+#include "../Application.h"
+#include "SkTypes.h"
+#include "SkUtils.h"
+#include "Window_android.h"
+
+namespace sk_app {
+
+static const int LOOPER_ID_MESSAGEPIPE = 1;
+
+void* pthread_main(void* arg);
+
+SkiaAndroidApp::SkiaAndroidApp() {
+    fNativeWindow = nullptr;
+    pthread_create(&fThread, nullptr, pthread_main, this);
+}
+
+SkiaAndroidApp::~SkiaAndroidApp() {
+    if (fWindow) {
+        fWindow->detach();
+    }
+    if (fNativeWindow) {
+        ANativeWindow_release(fNativeWindow);
+        fNativeWindow = nullptr;
+    }
+    if (fApp) {
+        delete fApp;
+    }
+}
+
+void SkiaAndroidApp::paintIfNeeded() {
+    if (fNativeWindow && fWindow) {
+        fWindow->onPaint();
+    }
+}
+
+void SkiaAndroidApp::postMessage(const Message& message) {
+    auto writeSize = write(fPipes[1], &message, sizeof(message));
+    SkASSERT(writeSize == sizeof(message));
+}
+
+void SkiaAndroidApp::readMessage(Message* message) {
+    auto readSize = read(fPipes[0], message, sizeof(Message));
+    SkASSERT(readSize == sizeof(Message));
+}
+
+static int message_callback(int fd, int events, void* data) {
+    auto skiaAndroidApp = (SkiaAndroidApp*)data;
+    Message message;
+    skiaAndroidApp->readMessage(&message);
+    SkDebugf("message_callback %d", message.fType);
+    SkASSERT(message.fType != kUndefined);
+
+    switch (message.fType) {
+        case kDestroyApp: {
+            delete skiaAndroidApp;
+            pthread_exit(nullptr);
+            return 0;
+        }
+        case kContentInvalidated: {
+            skiaAndroidApp->paintIfNeeded();
+            break;
+        }
+        case kSurfaceCreated: {
+            SkASSERT(!skiaAndroidApp->fNativeWindow && message.fNativeWindow);
+            skiaAndroidApp->fNativeWindow = message.fNativeWindow;
+            auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+            window_android->initDisplay(skiaAndroidApp->fNativeWindow);
+            skiaAndroidApp->paintIfNeeded();
+            break;
+        }
+        case kSurfaceChanged: {
+            SkASSERT(message.fNativeWindow == skiaAndroidApp->fNativeWindow &&
+                     message.fNativeWindow);
+            int width = ANativeWindow_getWidth(skiaAndroidApp->fNativeWindow);
+            int height = ANativeWindow_getHeight(skiaAndroidApp->fNativeWindow);
+            skiaAndroidApp->fWindow->onResize(width, height);
+            auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+            window_android->setContentRect(0, 0, width, height);
+            skiaAndroidApp->paintIfNeeded();
+            break;
+        }
+        case kSurfaceDestroyed: {
+            if (skiaAndroidApp->fNativeWindow) {
+                auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+                window_android->onDisplayDestroyed();
+                ANativeWindow_release(skiaAndroidApp->fNativeWindow);
+                skiaAndroidApp->fNativeWindow = nullptr;
+            }
+            break;
+        }
+        default: {
+            // do nothing
+        }
+    }
+
+    return 1;  // continue receiving callbacks
+}
+
+void* pthread_main(void* arg) {
+    SkDebugf("pthread_main begins");
+
+    auto skiaAndroidApp = (SkiaAndroidApp*)arg;
+
+    ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+    pipe(skiaAndroidApp->fPipes);
+    ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
+                  message_callback, skiaAndroidApp);
+
+    int ident;
+    int events;
+    struct android_poll_source* source;
+
+    skiaAndroidApp->fApp = Application::Create(0, nullptr, skiaAndroidApp);
+
+    while ((ident = ALooper_pollAll(-1, nullptr, &events, (void**)&source)) >= 0) {
+        SkDebugf("ALooper_pollAll ident=%d", ident);
+    }
+
+    return nullptr;
+}
+
+extern "C"  // extern "C" is needed for JNI (although the method itself is in C++)
+    JNIEXPORT jlong JNICALL
+    Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env, jobject activity) {
+    SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp;
+    return (jlong)((size_t)skiaAndroidApp);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
+    JNIEnv* env, jobject activity, jlong handle) {
+    auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+    skiaAndroidApp->postMessage(Message(kDestroyApp));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
+    JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+    auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+    Message message(kSurfaceCreated);
+    message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+    skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
+    JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+    auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+    Message message(kSurfaceChanged);
+    message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+    skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
+    JNIEnv* env, jobject activity, jlong handle) {
+    auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+    skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
+}
+
+}  // namespace sk_app
diff --git a/tools/viewer/sk_app/android/surface_glue_android.h b/tools/viewer/sk_app/android/surface_glue_android.h
new file mode 100644
index 0000000..aefe462
--- /dev/null
+++ b/tools/viewer/sk_app/android/surface_glue_android.h
@@ -0,0 +1,55 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef surface_glue_android_DEFINED
+#define surface_glue_android_DEFINED
+
+#include <pthread.h>
+
+#include <android/native_window_jni.h>
+
+#include "../Application.h"
+#include "../Window.h"
+
+namespace sk_app {
+
+enum MessageType {
+    kUndefined,
+    kSurfaceCreated,
+    kSurfaceChanged,
+    kSurfaceDestroyed,
+    kDestroyApp,
+    kContentInvalidated
+};
+
+struct Message {
+    MessageType fType = kUndefined;
+    ANativeWindow* fNativeWindow = nullptr;
+
+    Message() {}
+    Message(MessageType t) : fType(t) {}
+};
+
+struct SkiaAndroidApp {
+    int fPipes[2];  // 0 is the read message pipe, 1 is the write message pipe
+    Application* fApp;
+    Window* fWindow;
+    ANativeWindow* fNativeWindow;
+
+    SkiaAndroidApp();
+    ~SkiaAndroidApp();
+    void postMessage(const Message& message);
+    void readMessage(Message* message);
+    void paintIfNeeded();
+
+private:
+    pthread_t fThread;
+};
+
+}  // namespace sk_app
+
+#endif