VulkanViewer on Android
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1903253003

Review URL: https://codereview.chromium.org/1903253003
diff --git a/DEPS b/DEPS
index 1710af6..dcec234 100644
--- a/DEPS
+++ b/DEPS
@@ -53,6 +53,7 @@
   "third_party/externals/shaderc2/third_party/googletest" : "https://github.com/google/googletest.git@d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0",
   "third_party/externals/shaderc2/third_party/glslang" : "https://github.com/google/glslang.git@e1cd410d9c03a24c00c570c91a99cad88bb475d1",
   "third_party/externals/shaderc2/third_party/spirv-tools" : "https://github.com/KhronosGroup/SPIRV-Tools.git@009c4358b5a1c93203166b3ed60a548f63522e81",
+  "third_party/externals/shaderc2/third_party/android-cmake" : "https://github.com/taka-no-me/android-cmake.git@556cc14296c226f753a3778d99d8b60778b7df4f",
 }
 
 deps_os = {
diff --git a/gyp/most.gyp b/gyp/most.gyp
index d4babe1..9e8c3aa 100644
--- a/gyp/most.gyp
+++ b/gyp/most.gyp
@@ -59,6 +59,11 @@
                 'android_system.gyp:VisualBench_APK',
               ],
             }],
+            [ 'skia_vulkan == 1', {
+              'dependencies': [
+                'android_system.gyp:VulkanViewer_APK',
+              ],
+            }],
           ],
         }],
         ['skia_os == "ios"', {
@@ -79,7 +84,7 @@
             'skiaserve.gyp:skiaserve',
           ],
         }],
-        [ 'skia_vulkan == 0 or skia_os != "win"', {
+        [ 'skia_vulkan == 0 or skia_os != "win" or skia_os != "android"', {
           'dependencies!': [
             'vulkanviewer.gyp:vulkanviewer',
           ],
diff --git a/gyp/shaderc.gyp b/gyp/shaderc.gyp
index 7732e22..3202685 100644
--- a/gyp/shaderc.gyp
+++ b/gyp/shaderc.gyp
@@ -28,13 +28,14 @@
           'shaderc_project_type' : 'ninja',
         }],
       ],
-      'shaderc_out_path': '../out/<(CONFIGURATION_NAME)/shaderc_out_<(skia_arch_type)',
+      'shaderc_out_path': '<(PRODUCT_DIR)/shaderc_out_<(skia_arch_type)',
     },
     # Export out of nested variables.
     'shaderc_build_configuration': '<(shaderc_build_configuration)',
     'shaderc_project_type': '<(shaderc_project_type)',
     'shaderc_out_path': '<(shaderc_out_path)',
     'shaderc_lib_name': '<(shaderc_lib_name)',
+    'android_toolchain%': '',
     
     # On Windows the library winds up inside a 'Debug' or 'Release' dir, not so
     # with ninja project build.
@@ -62,12 +63,12 @@
           'outputs': [
              '<(shaderc_lib_full_path)',
           ],
-          'action': ['python', '../tools/build_shaderc.py', '-s', '../third_party/externals/shaderc2', '-o', '<(shaderc_out_path)', '-a', '<(skia_arch_type)', '-t', '<(shaderc_build_configuration)', '-p', '<(shaderc_project_type)'],
+          'action': ['python', '../tools/build_shaderc.py', '-s', '../third_party/externals/shaderc2', '-o', '<(shaderc_out_path)', '-a', '<(skia_arch_type)', '-t', '<(shaderc_build_configuration)', '-p', '<(shaderc_project_type)', '-c', '<(android_toolchain)'],
         },
       ],     
       'copies': [
         {
-          'destination': '../out/<(CONFIGURATION_NAME)',
+          'destination': '<(PRODUCT_DIR)',
           'files': ['<(shaderc_lib_full_path)'],
         },
       ],
diff --git a/gyp/vulkanviewer.gyp b/gyp/vulkanviewer.gyp
index fb67e55..91c175b 100644
--- a/gyp/vulkanviewer.gyp
+++ b/gyp/vulkanviewer.gyp
@@ -42,6 +42,27 @@
         'tools.gyp:timer',
         'tools.gyp:url_data_manager',
       ],
+      'conditions' : [
+        [ 'skia_os == "android"', {
+          'dependencies': [
+            'android_deps.gyp:Android_EntryPoint',
+            'android_deps.gyp:native_app_glue',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-landroid',
+            ],
+          },
+        }],
+        ['skia_os != "android"', {
+          'sources/': [ ['exclude', '_android.(h|cpp)$'],
+          ],
+        }],
+        ['skia_os != "win"', {
+          'sources/': [ ['exclude', '_win.(h|cpp)$'],
+          ],
+        }],
+      ],
     },
   ],
 }
diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle
index f8872ca..9d103b4 100644
--- a/platform_tools/android/apps/settings.gradle
+++ b/platform_tools/android/apps/settings.gradle
@@ -2,3 +2,4 @@
 include ':visualbench'
 include ':visualbenchsdl'
 include ':canvasproof'
+include ':vulkanviewer'
diff --git a/platform_tools/android/apps/vulkanviewer/build.gradle b/platform_tools/android/apps/vulkanviewer/build.gradle
new file mode 100644
index 0000000..acb6740
--- /dev/null
+++ b/platform_tools/android/apps/vulkanviewer/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+apply plugin: 'com.android.application'
+android {
+    compileSdkVersion 19
+    buildToolsVersion "22.0.1"
+    defaultConfig {
+        applicationId "org.skia.vulkanviewer"
+        minSdkVersion 9
+        targetSdkVersion 19
+        versionCode 1
+        versionName "1.0"
+        signingConfig signingConfigs.debug
+    }
+    sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
+    sourceSets.main.jniLibs.srcDir "src/main/libs"
+    productFlavors { arm {}; arm64 {}; x86 {}; x86_64 {}; mips {}; mips64 {}; }
+    applicationVariants.all{ variant ->
+        def buildNativeLib = task("${variant.name}_NativeLib", type:Exec) {
+            workingDir '../../../..' // top-level skia directory
+            commandLine constructBuildCommand(variant, "CopyVulkanViewerDeps").split()
+            environment PATH: getPathWithDepotTools()
+            environment ANDROID_SDK_ROOT: getSDKPath()
+        }
+        buildNativeLib.onlyIf { !project.hasProperty("suppressNativeBuild") }
+        TaskCollection<Task> assembleTask
+        assembleTask = project.tasks.matching {
+            it.name.contains("assemble") &&
+                    it.name.toLowerCase().endsWith(variant.name.toLowerCase())
+        }
+        assembleTask.getAt(0).dependsOn buildNativeLib
+    }
+}
diff --git a/platform_tools/android/apps/vulkanviewer/src/main/AndroidManifest.xml b/platform_tools/android/apps/vulkanviewer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3220c31
--- /dev/null
+++ b/platform_tools/android/apps/vulkanviewer/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- BEGIN_INCLUDE(manifest) -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.skia.vulkanviewer"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+  <application
+      android:allowBackup="false"
+      android:label="VulkanViewer">
+
+    <activity android:name=".VulkanViewerActivity"
+              android:label="VulkanViewer"
+              android:screenOrientation="portrait"
+              android:configChanges="orientation|keyboardHidden">
+      <meta-data android:name="android.app.lib_name"
+                 android:value="vulkanviewer" />
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+
+</manifest>
+<!-- END_INCLUDE(manifest) -->
diff --git a/platform_tools/android/apps/vulkanviewer/src/main/java/com/skia/vulkanviewer/VulkanViewerActivity.java b/platform_tools/android/apps/vulkanviewer/src/main/java/com/skia/vulkanviewer/VulkanViewerActivity.java
new file mode 100644
index 0000000..dcb0085
--- /dev/null
+++ b/platform_tools/android/apps/vulkanviewer/src/main/java/com/skia/vulkanviewer/VulkanViewerActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.skia.vulkanviewer;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.View;
+import android.view.WindowManager;
+
+public class VulkanViewerActivity extends android.app.NativeActivity {
+    static {
+        System.loadLibrary("skia_android");
+    }
+    
+    @Override
+    public void onCreate(Bundle savedInstanceState) 
+    {
+        super.onCreate(savedInstanceState);
+        ActionBar ab = this.getActionBar();
+        ab.hide();
+    }
+}
diff --git a/platform_tools/android/bin/android_setup.sh b/platform_tools/android/bin/android_setup.sh
index fd05a98..0084fc6 100755
--- a/platform_tools/android/bin/android_setup.sh
+++ b/platform_tools/android/bin/android_setup.sh
@@ -38,6 +38,8 @@
     LOGCAT=1
   elif [[ "$1" == "--verbose" ]]; then
     VERBOSE="true"
+  elif [[ "$1" == "--vulkan" ]]; then
+    SKIA_VULKAN="true" 
   else
     APP_ARGS=("${APP_ARGS[@]}" "${1}")
   fi
@@ -48,6 +50,10 @@
   export GYP_DEFINES="skia_clang_build=1 $GYP_DEFINES"
 fi
 
+if [ "$SKIA_VULKAN" == "true" ]; then
+  export GYP_DEFINES="skia_vulkan=1 $GYP_DEFINES"
+fi
+
 function verbose {
   if [[ -n $VERBOSE ]]; then
     echo $@
@@ -167,7 +173,7 @@
     source $SCRIPT_DIR/utils/setup_toolchain.sh
   fi
 
-  DEFINES="${DEFINES} android_toolchain=${TOOLCHAIN_TYPE}"
+  DEFINES="${DEFINES} android_toolchain=${ANDROID_TOOLCHAIN}"
   DEFINES="${DEFINES} android_buildtype=${BUILDTYPE}"
   exportVar GYP_DEFINES "$DEFINES $GYP_DEFINES"
 
diff --git a/platform_tools/android/bin/utils/setup_toolchain.sh b/platform_tools/android/bin/utils/setup_toolchain.sh
index 50098ba..6851931 100755
--- a/platform_tools/android/bin/utils/setup_toolchain.sh
+++ b/platform_tools/android/bin/utils/setup_toolchain.sh
@@ -42,6 +42,10 @@
   else
       API=14  # Android 4.0
   fi
+  
+  if [ "$SKIA_VULKAN" == "true" ]; then
+      API=24  # Android N Preview
+  fi
 
   TOOLCHAIN=$ANDROID_ARCH-$NDK-$API
   HOST=`uname | tr '[A-Z]' '[a-z]'`
diff --git a/platform_tools/android/gyp/skia_android.gypi b/platform_tools/android/gyp/skia_android.gypi
index 8833afe..7767183 100644
--- a/platform_tools/android/gyp/skia_android.gypi
+++ b/platform_tools/android/gyp/skia_android.gypi
@@ -45,7 +45,10 @@
       }],
     ],
   },
-  'includes' : [ 'canvasproof.gypi', ],
+  'includes' : [
+      'canvasproof.gypi',
+      'vulkanviewer.gypi',
+  ],
   'targets': [
     {
       'target_name': 'CopySampleAppDeps',
diff --git a/platform_tools/android/gyp/vulkanviewer.gypi b/platform_tools/android/gyp/vulkanviewer.gypi
new file mode 100644
index 0000000..adcfd22
--- /dev/null
+++ b/platform_tools/android/gyp/vulkanviewer.gypi
@@ -0,0 +1,65 @@
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+{
+  'targets': [
+    {
+      'target_name': 'CopyVulkanViewerDeps',
+      'type': 'none',
+      'dependencies': [
+        'skia_lib.gyp:skia_lib',
+        'vulkanviewer.gyp:vulkanviewer',
+      ],
+      'copies': [
+        {
+          'destination': '<(android_base)/apps/vulkanviewer/src/main/libs/<(android_arch)',
+          'conditions': [
+            [ 'skia_shared_lib', {
+              'files': [
+                '<(SHARED_LIB_DIR)/libskia_android.so',
+                '<(SHARED_LIB_DIR)/libvulkanviewer.so',
+              ]}, {
+              'files': [
+                '<(SHARED_LIB_DIR)/libvulkanviewer.so',
+              ]}
+           ],
+          ],
+        },
+      ],
+    },
+    {
+      'target_name': 'VulkanViewer_APK',
+      'type': 'none',
+      'dependencies': [ 'CopyVulkanViewerDeps', ],
+      'actions': [
+        {
+          'action_name': 'SkiaVulkanViewer_apk',
+          'inputs': [
+            '<(android_base)/apps/vulkanviewer/src/main/AndroidManifest.xml',
+            '<(android_base)/apps/vulkanviewer/src/main/java/com/skia/vulkanviewer/VulkanViewerActivity.java',
+            '<(android_base)/apps/vulkanviewer/src/main/libs/<(android_arch)/libvulkanviewer.so',
+
+          ],
+          'conditions': [
+            [ 'skia_shared_lib', {
+              'inputs': [
+                '<(android_base)/apps/vulkanviewer/src/main/libs/<(android_arch)/libskia_android.so',
+              ],
+            }],
+          ],
+          'outputs': [
+            '../apps/vulkanviewer/build/outputs/apk/',
+          ],
+          'action': [
+            '<(android_base)/apps/gradlew',
+            ':vulkanviewer:assemble<(android_variant)Debug',
+            '-p<(android_base)/apps/vulkanviewer',
+            '-PsuppressNativeBuild',
+            '--daemon',
+          ],
+        },
+      ],
+    },
+  ],
+}
diff --git a/platform_tools/android/third_party/native_app_glue/android_native_app_glue.c b/platform_tools/android/third_party/native_app_glue/android_native_app_glue.c
index 55a52bc..595bc76 100644
--- a/platform_tools/android/third_party/native_app_glue/android_native_app_glue.c
+++ b/platform_tools/android/third_party/native_app_glue/android_native_app_glue.c
@@ -419,6 +419,13 @@
     android_app_set_input((struct android_app*)activity->instance, NULL);
 }
 
+static void onContentRectChanged(ANativeActivity* activity, const ARect* rect) {
+	struct android_app* android_app = (struct android_app*)activity->instance;
+    LOGV("OnContentRectChanged: %p\n", activity);
+    android_app->contentRect = *rect;
+    android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED);
+}
+
 void ANativeActivity_onCreate(ANativeActivity* activity,
         void* savedState, size_t savedStateSize) {
     LOGV("Creating: %p\n", activity);
@@ -435,6 +442,7 @@
     activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
     activity->callbacks->onInputQueueCreated = onInputQueueCreated;
     activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
+    activity->callbacks->onContentRectChanged = onContentRectChanged;
 
     activity->instance = android_app_create(activity, savedState, savedStateSize);
 }
diff --git a/tools/build_shaderc.py b/tools/build_shaderc.py
index 93b42bd..0a3fc3a 100644
--- a/tools/build_shaderc.py
+++ b/tools/build_shaderc.py
@@ -29,6 +29,8 @@
       'Directory for cmake build')
   parser.add_argument('-p', '--project_type', required=True, help=
       'Project type to use. Must be "ninja", "MSVS2013", or "MSVS2015"')
+  parser.add_argument('-c', '--android_toolchain', required=False, help=
+      'Location of standalone android toolchain to use for crosscompiling')
   args = parser.parse_args()
 
   args.src_dir = os.path.abspath(args.src_dir)
@@ -44,12 +46,9 @@
   else:
     args.exit('Invalid build type: ' + args.build_type);
 
-  if args.arch_type == 'x86':
-    vs_arch = ''
-  elif args.arch_type == 'x86_64':
+  vs_arch = ''
+  if args.arch_type == 'x86_64':
     vs_arch = ' Win64'
-  else:
-    sys.exit('Invalid arch type: ' + args.arch_type);
 
   if args.project_type == 'ninja':
     generator = 'Ninja'
@@ -70,9 +69,17 @@
 
   try:
     build_type_arg='-DCMAKE_BUILD_TYPE=' + args.build_type
-    subprocess.check_call(['cmake', '-G', generator,
-        '-DSPIRV_SKIP_EXECUTABLES=ON', '-DSHADERC_ENABLE_SHARED_CRT=ON',
-        args.src_dir, build_type_arg], cwd=args.output_dir)
+    cmake_cmd = ['cmake', '-G', generator,
+                 '-DSPIRV_SKIP_EXECUTABLES=ON',
+                 '-DSHADERC_ENABLE_SHARED_CRT=ON']
+    if args.android_toolchain and args.android_toolchain.strip() :
+      cmake_cmd.append('-DCMAKE_TOOLCHAIN_FILE=' + args.src_dir +\
+                       '/third_party/android-cmake/android.toolchain.cmake')
+      cmake_cmd.append('-DANDROID_TOOLCHAIN_NAME=standalone-clang')
+      cmake_cmd.append('-DANDROID_STANDALONE_TOOLCHAIN=' +\
+                       os.path.abspath(args.android_toolchain))
+    cmake_cmd.extend([build_type_arg, args.src_dir])
+    subprocess.check_call(cmake_cmd, cwd=args.output_dir)
   except subprocess.CalledProcessError as error:
     sys.exit('Error (ret code: {code}) calling "{cmd}" in {dir}'.format(
         code = error.returncode, cmd = error.cmd, dir = args.src_dir))
@@ -86,4 +93,3 @@
 
 if __name__ == '__main__':
   main()
-
diff --git a/tools/vulkan/Window.h b/tools/vulkan/Window.h
index c8971ee..387f052 100644
--- a/tools/vulkan/Window.h
+++ b/tools/vulkan/Window.h
@@ -9,6 +9,7 @@
 #define Window_DEFINED
 
 #include "SkTypes.h"
+#include "SkRect.h"
 
 class SkCanvas;
 class VulkanTestContext;
@@ -23,6 +24,10 @@
     virtual void show() = 0;
     virtual void inval() = 0;
 
+    virtual bool scaleContentToFit() const { return false; }
+    virtual bool supportsContentRect() const { return false; }
+    virtual SkRect getContentRect() { return SkRect::MakeEmpty(); }
+
     struct AttachmentInfo {
         int fSampleCount;
         int fStencilBits;
diff --git a/tools/vulkan/android/VulkanTestContext_android.cpp b/tools/vulkan/android/VulkanTestContext_android.cpp
new file mode 100644
index 0000000..522b839
--- /dev/null
+++ b/tools/vulkan/android/VulkanTestContext_android.cpp
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "VulkanTestContext_android.h"
+
+#include "vk/GrVkInterface.h"
+#include "../../src/gpu/vk/GrVkUtil.h"
+
+VkSurfaceKHR VulkanTestContext::createVkSurface(void* platformData) {
+    // need better error handling here
+    SkASSERT(platformData);
+    ContextPlatformData_android* androidPlatformData =
+                                           reinterpret_cast<ContextPlatformData_android*>(platformData);
+    VkSurfaceKHR surface;
+
+    VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
+    memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
+    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+    surfaceCreateInfo.pNext = nullptr;
+    surfaceCreateInfo.flags = 0;
+    surfaceCreateInfo.window = androidPlatformData->fNativeWindow;
+
+    VkResult res = GR_VK_CALL(fBackendContext->fInterface,
+                              CreateAndroidSurfaceKHR(fBackendContext->fInstance,
+                                                      &surfaceCreateInfo,
+                                                      nullptr, &surface));
+    return (VK_SUCCESS == res) ? surface : VK_NULL_HANDLE;
+}
+
+bool VulkanTestContext::canPresent(uint32_t queueFamilyIndex) {
+    return true;
+}
diff --git a/tools/vulkan/android/VulkanTestContext_android.h b/tools/vulkan/android/VulkanTestContext_android.h
new file mode 100644
index 0000000..66ed35c
--- /dev/null
+++ b/tools/vulkan/android/VulkanTestContext_android.h
@@ -0,0 +1,23 @@
+
+/*
+ * 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 VULKANTESTCONTEXT_ANDROID_DEFINED
+#define VULKANTESTCONTEXT_ANDROID_DEFINED
+
+#ifdef SK_VULKAN
+
+#include "../VulkanTestContext.h"
+
+struct ANativeWindow;
+
+struct ContextPlatformData_android {
+    ANativeWindow* fNativeWindow;
+};
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/vulkan/android/Window_android.cpp b/tools/vulkan/android/Window_android.cpp
new file mode 100644
index 0000000..fb87a3f
--- /dev/null
+++ b/tools/vulkan/android/Window_android.cpp
@@ -0,0 +1,179 @@
+/*
+* 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 "Window_android.h"
+
+#include "VulkanTestContext_android.h"
+
+Window* Window::CreateNativeWindow(void* platformData) {
+    Window_android* window = new Window_android();
+    if (!window->init((android_app*)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;
+    return true;
+}
+
+void Window_android::setTitle(const char* title) {
+    //todo
+    SkDebugf("Title: %s", title);
+}
+
+bool Window_android::attach(BackEndType attachType, int msaaSampleCount, AttachmentInfo*) {
+    if (kVulkan_BackendType != attachType) {
+        return false;
+    }
+
+    mSampleCount = msaaSampleCount;
+
+    // We delay the creation of fTestContext until Android informs us that
+    // the native window is ready to use.
+    return true;
+}
+
+void Window_android::initDisplay(ANativeWindow* window) {
+    SkASSERT(window);
+    ContextPlatformData_android platformData;
+    platformData.fNativeWindow = window;
+    fTestContext = VulkanTestContext::Create((void*)&platformData, mSampleCount);
+}
+
+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::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;
+}
diff --git a/tools/vulkan/android/Window_android.h b/tools/vulkan/android/Window_android.h
new file mode 100644
index 0000000..a4805bd
--- /dev/null
+++ b/tools/vulkan/android/Window_android.h
@@ -0,0 +1,48 @@
+/*
+* 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 Window_android_DEFINED
+#define Window_android_DEFINED
+
+#include "../Window.h"
+#include <android_native_app_glue.h>
+
+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);
+    void initDisplay(ANativeWindow* window);
+
+    void setTitle(const char*) override;
+    void show() override {}
+
+    bool attach(BackEndType attachType, int msaaSampleCount, AttachmentInfo*) 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); }
+
+private:
+    android_app* mApp = nullptr;
+    SkRect mContentRect;
+    int mSampleCount = 0;
+};
+
+#endif
diff --git a/tools/vulkan/android/main_android.cpp b/tools/vulkan/android/main_android.cpp
new file mode 100644
index 0000000..783cd1f
--- /dev/null
+++ b/tools/vulkan/android/main_android.cpp
@@ -0,0 +1,67 @@
+/*
+* 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 <jni.h>
+#include <errno.h>
+
+#include <android_native_app_glue.h>
+
+#include "../Application.h"
+#include "Timer.h"
+
+static double now_ms() { return SkTime::GetNSecs() * 1e-6; }
+
+/**
+ * This is the main entry point of a native application that is using
+ * android_native_app_glue.  It runs in its own thread, with its own
+ * event loop for receiving input events and doing other things.
+ */
+void android_main(struct android_app* state) {
+    // Make sure glue isn't stripped.
+    app_dummy();
+
+    static const char* gCmdLine[] = {
+        "vulkanviewer",
+        "--skps",
+        "/data/local/tmp/skp",
+    };
+
+    std::unique_ptr<Application> vkApp(Application::Create(SK_ARRAY_COUNT(gCmdLine),
+                                                           const_cast<char**>(gCmdLine),
+                                                           state));
+
+    double currentTime = 0.0;
+    double previousTime = 0.0;
+
+    // loop waiting for stuff to do.
+    while (1) {
+        // Read all pending events.
+        int ident;
+        int events;
+        struct android_poll_source* source;
+
+        // block forever waiting for events.
+        while ((ident=ALooper_pollAll(-1, NULL, &events,
+                (void**)&source)) >= 0) {
+
+            // Process this event.
+            if (source != NULL) {
+                source->process(state, source);
+            }
+
+            // Check if we are exiting.
+            if (state->destroyRequested != 0) {
+                return;
+            }
+
+            previousTime = currentTime;
+            currentTime = now_ms();
+            vkApp->onIdle(currentTime - previousTime);
+        }
+    }
+}
+//END_INCLUDE(all)
diff --git a/tools/vulkan/viewer/GMSlide.h b/tools/vulkan/viewer/GMSlide.h
index c9ed084..6b03527 100644
--- a/tools/vulkan/viewer/GMSlide.h
+++ b/tools/vulkan/viewer/GMSlide.h
@@ -16,6 +16,8 @@
     GMSlide(skiagm::GM* gm);
     ~GMSlide() override;
 
+    SkISize getDimensions() const override { return fGM->getISize(); }
+
     void draw(SkCanvas* canvas) override;
     bool animate(const SkAnimTimer&) override;
 
diff --git a/tools/vulkan/viewer/SKPSlide.h b/tools/vulkan/viewer/SKPSlide.h
index 73fd5a1..42845fa 100644
--- a/tools/vulkan/viewer/SKPSlide.h
+++ b/tools/vulkan/viewer/SKPSlide.h
@@ -16,6 +16,8 @@
     SKPSlide(const SkString& name, const SkString& path);
     ~SKPSlide() override;
 
+    SkISize getDimensions() const override { return fCullRect.size(); }
+
     void draw(SkCanvas* canvas) override;
     void load() override;
     void unload() override;
diff --git a/tools/vulkan/viewer/Slide.h b/tools/vulkan/viewer/Slide.h
index f75e3fd..cdc225b 100644
--- a/tools/vulkan/viewer/Slide.h
+++ b/tools/vulkan/viewer/Slide.h
@@ -9,6 +9,7 @@
 #define Slide_DEFINED
 
 #include "SkRefCnt.h"
+#include "SkSize.h"
 #include "SkString.h"
 
 class SkCanvas;
@@ -18,6 +19,8 @@
 public:
     virtual ~Slide() {}
 
+    virtual SkISize getDimensions() const = 0;
+
     virtual void draw(SkCanvas* canvas) = 0;
     virtual bool animate(const SkAnimTimer&) { return false;  }
     virtual void load() {}
diff --git a/tools/vulkan/viewer/VulkanViewer.cpp b/tools/vulkan/viewer/VulkanViewer.cpp
index a88a337..faa3014 100644
--- a/tools/vulkan/viewer/VulkanViewer.cpp
+++ b/tools/vulkan/viewer/VulkanViewer.cpp
@@ -81,10 +81,12 @@
     // set up slides
     this->initSlides();
 
+    fAnimTimer.run();
+
     // set up first frame
     fCurrentSlide = 0;
     setupCurrentSlide(-1);
-    fLocalMatrix.reset();
+    updateMatrix();
 
     fWindow->show();
 }
@@ -254,10 +256,24 @@
 
 void VulkanViewer::onPaint(SkCanvas* canvas) {
 
-    canvas->clear(SK_ColorWHITE);
-
     int count = canvas->save();
-    canvas->setMatrix(fLocalMatrix);
+
+    if (fWindow->supportsContentRect()) {
+        SkRect contentRect = fWindow->getContentRect();
+        canvas->clipRect(contentRect);
+        canvas->translate(contentRect.fLeft, contentRect.fTop);
+    }
+
+    canvas->clear(SK_ColorWHITE);
+    if (fWindow->supportsContentRect() && fWindow->scaleContentToFit()) {
+        const SkRect contentRect = fWindow->getContentRect();
+        const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
+        const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
+        SkMatrix matrix;
+        matrix.setRectToRect(slideBounds, contentRect, SkMatrix::kCenter_ScaleToFit);
+        canvas->concat(matrix);
+    }
+    canvas->concat(fLocalMatrix);
 
     fSlides[fCurrentSlide]->draw(canvas);
     canvas->restoreToCount(count);
@@ -282,6 +298,12 @@
     SkPaint paint;
     canvas->save();
 
+    if (fWindow->supportsContentRect()) {
+        SkRect contentRect = fWindow->getContentRect();
+        canvas->clipRect(contentRect);
+        canvas->translate(contentRect.fLeft, contentRect.fTop);
+    }
+
     canvas->clipRect(rect);
     paint.setColor(SK_ColorBLACK);
     canvas->drawRect(rect, paint);