check in of the canny filter demo

Change-Id: I2482c9a0442fd213b9ce25f06268f0eeb765b326
diff --git a/java/tests/CannyLive/Android.mk b/java/tests/CannyLive/Android.mk
new file mode 100644
index 0000000..2e50bec
--- /dev/null
+++ b/java/tests/CannyLive/Android.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
+LOCAL_RENDERSCRIPT_TARGET_API := 22
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+LOCAL_SDK_VERSION := 23
+
+LOCAL_PACKAGE_NAME := CannyLive
+
+include $(BUILD_PACKAGE)
diff --git a/java/tests/CannyLive/AndroidManifest.xml b/java/tests/CannyLive/AndroidManifest.xml
new file mode 100644
index 0000000..e7e3299
--- /dev/null
+++ b/java/tests/CannyLive/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>

+<manifest xmlns:android="http://schemas.android.com/apk/res/android"

+    package="com.android.example.cannylive" >

+    <uses-feature android:name="android.hardware.camera" />

+    <uses-feature

+        android:name="android.hardware.camera.front"

+        android:required="false" />

+    <uses-permission android:name="android.permission.CAMERA" />

+    <uses-permission android:name="android.permission.RECORD_AUDIO" />

+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

+    <application

+        android:allowBackup="true"

+        android:icon="@drawable/camera"

+        android:label="@string/app_name"

+        android:theme="@style/AppTheme" >

+        <activity

+            android:name="com.android.example.cannylive.MainActivity"

+            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/java/tests/CannyLive/_index.html b/java/tests/CannyLive/_index.html
new file mode 100644
index 0000000..a1cabef
--- /dev/null
+++ b/java/tests/CannyLive/_index.html
@@ -0,0 +1,6 @@
+<h1>RenderScript Camera Demo</h1>
+<h2>Am example camera with live processing in RenderScript</h2>
+<p>
+This demonstrates a Canny filter in RenderScript filtering the camera preview.
+</p>
+
diff --git a/java/tests/CannyLive/res/anim/slide_in_from_left.xml b/java/tests/CannyLive/res/anim/slide_in_from_left.xml
new file mode 100644
index 0000000..75d05bb
--- /dev/null
+++ b/java/tests/CannyLive/res/anim/slide_in_from_left.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <translate
+        android:fromXDelta="-100%"
+        android:toXDelta="0%"
+        android:fromYDelta="0%"
+        android:toYDelta="0%"
+        android:duration="500" />
+
+</set>
diff --git a/java/tests/CannyLive/res/anim/slide_out_to_right.xml b/java/tests/CannyLive/res/anim/slide_out_to_right.xml
new file mode 100644
index 0000000..20ee06f
--- /dev/null
+++ b/java/tests/CannyLive/res/anim/slide_out_to_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+    <translate
+    android:fromXDelta="0%"
+    android:toXDelta="100%"
+    android:fromYDelta="0%"
+    android:toYDelta="0%"
+    android:duration="500"/>
+
+</set>
\ No newline at end of file
diff --git a/java/tests/CannyLive/res/drawable-hdpi/camera.png b/java/tests/CannyLive/res/drawable-hdpi/camera.png
new file mode 100644
index 0000000..38935c2
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable-hdpi/camera.png
Binary files differ
diff --git a/java/tests/CannyLive/res/drawable-mdpi/camera.png b/java/tests/CannyLive/res/drawable-mdpi/camera.png
new file mode 100644
index 0000000..de7c1de
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable-mdpi/camera.png
Binary files differ
diff --git a/java/tests/CannyLive/res/drawable-xhdpi/camera.png b/java/tests/CannyLive/res/drawable-xhdpi/camera.png
new file mode 100644
index 0000000..296ba2e
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable-xhdpi/camera.png
Binary files differ
diff --git a/java/tests/CannyLive/res/drawable-xxhdpi/camera.png b/java/tests/CannyLive/res/drawable-xxhdpi/camera.png
new file mode 100644
index 0000000..169ccfc
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable-xxhdpi/camera.png
Binary files differ
diff --git a/java/tests/CannyLive/res/drawable/ic_back.xml b/java/tests/CannyLive/res/drawable/ic_back.xml
new file mode 100644
index 0000000..1cbfdb2
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable/ic_back.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:autoMirrored="true"
+     >
+    <!--android:tint="?attr/colorControlNormal">-->
+    <path
+        android:pathData="M20,11L7.8,11l5.6,-5.6L12,4l-8,8l8,8l1.4,-1.4L7.8,13L20,13L20,11z"
+        android:fillColor="@android:color/black"/>
+</vector>
diff --git a/java/tests/CannyLive/res/drawable/ic_cam.xml b/java/tests/CannyLive/res/drawable/ic_cam.xml
new file mode 100644
index 0000000..fbeedc6
--- /dev/null
+++ b/java/tests/CannyLive/res/drawable/ic_cam.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12.0,12.0m-3.2,0.0a3.2,3.2 0.0,1.0 1.0,6.4 0.0a3.2,3.2 0.0,1.0 1.0,-6.4 0.0"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9.0,2.0l-1.83,2.0l-3.17,0.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,12.0c0.0,1.0 0.9,2.0 2.0,2.0l16.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0l0.0,-12.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-3.17,0.0l-1.83,-2.0l-6.0,0.0zm3.0,15.0c-2.76,0.0 -5.0,-2.24 -5.0,-5.0s2.24,-5.0 5.0,-5.0 5.0,2.24 5.0,5.0 -2.24,5.0 -5.0,5.0z"/>
+
+</vector>
\ No newline at end of file
diff --git a/java/tests/CannyLive/res/layout/activity_main.xml b/java/tests/CannyLive/res/layout/activity_main.xml
new file mode 100644
index 0000000..60e049a
--- /dev/null
+++ b/java/tests/CannyLive/res/layout/activity_main.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<!-- Copyright (C) 2015 The Android Open Source Project

+

+     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.

+-->

+

+<LinearLayout

+    android:orientation="horizontal"

+    android:layout_height="match_parent"

+    android:layout_width="match_parent"

+    xmlns:tools="http://schemas.android.com/tools"

+    xmlns:custom="http://schemas.android.com/apk/res-auto"

+    xmlns:android="http://schemas.android.com/apk/res/android"

+    android:id="@+id/panels">

+

+    <com.android.example.cannylive.CameraView

+        android:layout_height="wrap_content"

+        android:layout_width="0dp"

+        android:id="@+id/preview"

+        custom:aspectRatio="1.333"

+        android:layout_weight="4"

+        android:layout_gravity="center_vertical" />

+

+

+

+        <LinearLayout

+            android:orientation="vertical"

+            android:layout_height="match_parent"

+            android:layout_width="0px"

+            android:layout_weight="1"

+            android:id="@+id/control_bar_contents">

+

+

+            <Button

+                android:layout_height="wrap_content"

+                android:layout_width="match_parent"

+                android:layout_gravity="center_horizontal"

+                android:onClick="changeEffectMode"

+                android:text="mode" />

+

+            <ImageButton

+                android:layout_height="wrap_content"

+                android:layout_width="match_parent"

+                android:layout_gravity="center_horizontal"

+                android:onClick="capture"

+                android:src="@drawable/ic_cam" />

+

+

+    </LinearLayout>

+

+</LinearLayout>

diff --git a/java/tests/CannyLive/res/layout/layout.xml b/java/tests/CannyLive/res/layout/layout.xml
new file mode 100644
index 0000000..a840496
--- /dev/null
+++ b/java/tests/CannyLive/res/layout/layout.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="horizontal"
+              android:paddingStart="8dip"
+              android:paddingTop="6dip"
+              android:paddingBottom="6dip">
+
+    <RadioButton
+        android:id="@+id/b1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/tv1"
+        android:layout_toRightOf="@+id/b1"
+        android:text="Normal Flow"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/tv2"
+        android:layout_toRightOf="@+id/b1"
+        android:layout_below="@+id/tv1"
+        android:text="Use this for most bugs for the best beug reporting experience\n"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <RadioButton
+        android:id="@+id/b2"
+        android:layout_below="@+id/tv2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/tv3"
+        android:text="Emergency report"
+        android:layout_toRightOf="@+id/b2"
+        android:layout_below="@+id/tv2"
+        android:textStyle="bold"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/tv4"
+        android:text="Use this for bugs about device unresponsiveness or slowness this is a striped down experience to capture the bug in and uninterrupted device state."
+        android:layout_toRightOf="@+id/b2"
+        android:layout_below="@+id/tv3"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+
+</RelativeLayout>
diff --git a/java/tests/CannyLive/res/mipmap-hdpi/ic_launcher.png b/java/tests/CannyLive/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/java/tests/CannyLive/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/CannyLive/res/mipmap-mdpi/ic_launcher.png b/java/tests/CannyLive/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/java/tests/CannyLive/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/CannyLive/res/mipmap-xhdpi/ic_launcher.png b/java/tests/CannyLive/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/java/tests/CannyLive/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/CannyLive/res/mipmap-xxhdpi/ic_launcher.png b/java/tests/CannyLive/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/java/tests/CannyLive/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/CannyLive/res/mipmap-xxxhdpi/ic_launcher.png b/java/tests/CannyLive/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/java/tests/CannyLive/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/java/tests/CannyLive/res/values-v21/styles.xml b/java/tests/CannyLive/res/values-v21/styles.xml
new file mode 100644
index 0000000..f9166cc
--- /dev/null
+++ b/java/tests/CannyLive/res/values-v21/styles.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>

+<!-- Copyright (C) 2015 The Android Open Source Project

+

+     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.

+-->

+<resources>

+    <style name="AppTheme" parent="android:Theme.Material.Light">

+    </style>

+</resources>

diff --git a/java/tests/CannyLive/res/values-w820dp/dimens.xml b/java/tests/CannyLive/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..62df187
--- /dev/null
+++ b/java/tests/CannyLive/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>

+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml

+         (such as screen margins) for screens with more than 820dp of available width. This

+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->

+    <dimen name="activity_horizontal_margin">64dp</dimen>

+</resources>

diff --git a/java/tests/CannyLive/res/values/attrs.xml b/java/tests/CannyLive/res/values/attrs.xml
new file mode 100644
index 0000000..9221607
--- /dev/null
+++ b/java/tests/CannyLive/res/values/attrs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+
+<resources>
+    <declare-styleable name="FixedAspectSurfaceView">
+        <attr name="aspectRatio" format="float"/>
+    </declare-styleable>
+</resources>
diff --git a/java/tests/CannyLive/res/values/base-strings.xml b/java/tests/CannyLive/res/values/base-strings.xml
new file mode 100644
index 0000000..d8f8952
--- /dev/null
+++ b/java/tests/CannyLive/res/values/base-strings.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+
+<resources>
+    <string name="app_name">RS Camera</string>
+    <string name="intro_message">
+        <![CDATA[
+        
+            
+            This demo implements a real-time high-dynamic-range camera viewfinder, by alternating
+            the sensor\'s exposure time between two exposure values on even and odd frames, and then
+            compositing together the latest two frames whenever a new frame is captured.
+            
+        
+        ]]>
+    </string>
+</resources>
diff --git a/java/tests/CannyLive/res/values/colors.xml b/java/tests/CannyLive/res/values/colors.xml
new file mode 100644
index 0000000..2a12c47
--- /dev/null
+++ b/java/tests/CannyLive/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>

+<resources>

+    <color name="colorPrimary">#3F51B5</color>

+    <color name="colorPrimaryDark">#303F9F</color>

+    <color name="colorAccent">#FF4081</color>

+</resources>

diff --git a/java/tests/CannyLive/res/values/dimens.xml b/java/tests/CannyLive/res/values/dimens.xml
new file mode 100644
index 0000000..295b5a9
--- /dev/null
+++ b/java/tests/CannyLive/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>

+    <!-- Default screen margins, per the Android Design guidelines. -->

+    <dimen name="activity_horizontal_margin">16dp</dimen>

+    <dimen name="activity_vertical_margin">16dp</dimen>

+</resources>

diff --git a/java/tests/CannyLive/res/values/strings.xml b/java/tests/CannyLive/res/values/strings.xml
new file mode 100644
index 0000000..21838d5
--- /dev/null
+++ b/java/tests/CannyLive/res/values/strings.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2013 The Android Open Source Project
+
+     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.
+-->
+
+<resources>
+
+    <string name="help_button">Help</string>
+
+    <string-array name="mode_label_array">
+        <!-- must be in same order as ViewfinderProcessor.MODE_ ints -->
+        <item>Mode: Normal</item>
+        <item>Mode: Split</item>
+        <item>Mode: HDR</item>
+    </string-array>
+
+    <string name="auto_exposure_label">Auto exp. time:</string>
+    <string name="even_exposure_label">Even exp. time:</string>
+    <string name="odd_exposure_label">Odd exp. time:</string>
+
+    <string name="help_text">
+      <b>HDR Viewfinder Demo:</b>\n\n
+
+      Tap viewfinder to switch modes.\n\n
+
+      <b>Normal:</b> Standard camera preview\n
+      <b>Split:</b> Manual exposure control\n
+      <b>HDR:</b> Fused HDR viewfinder\n\n
+
+      Swipe up/down in Split/HDR modes to change manual exposure
+      values.\n\n
+
+      The left half of the viewfinder controls exposure time for
+      even-numbered frames, and the right half of the viewfinder
+      controls exposure time for odd-numbered frames
+    </string>
+
+    <string name="info">Info</string>
+
+    <string name="camera_no_good">No back-facing sufficiently capable camera available!</string>
+    <string name="camera_disabled">Camera is disabled by device policy</string>
+    <string name="camera_disconnected">Camera was disconnected before it was opened</string>
+    <string name="camera_error">Camera service reported an error</string>
+    <string name="camera_unknown">Unknown camera error: %s</string>
+
+</resources>
diff --git a/java/tests/CannyLive/res/values/styles.xml b/java/tests/CannyLive/res/values/styles.xml
new file mode 100644
index 0000000..12eb119
--- /dev/null
+++ b/java/tests/CannyLive/res/values/styles.xml
@@ -0,0 +1,8 @@
+<resources>

+

+    <!-- Base application theme. -->

+    <style name="AppTheme" parent="android:Theme.Light">

+        <!-- Customize your theme here. -->

+    </style>

+

+</resources>

diff --git a/java/tests/CannyLive/res/values/template-dimens.xml b/java/tests/CannyLive/res/values/template-dimens.xml
new file mode 100644
index 0000000..291c495
--- /dev/null
+++ b/java/tests/CannyLive/res/values/template-dimens.xml
@@ -0,0 +1,31 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+
+<resources>
+
+    <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+    <dimen name="margin_tiny">4dp</dimen>
+    <dimen name="margin_small">8dp</dimen>
+    <dimen name="margin_medium">16dp</dimen>
+    <dimen name="margin_large">32dp</dimen>
+    <dimen name="margin_huge">64dp</dimen>
+
+    <!-- Semantic definitions -->
+
+    <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+    <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/java/tests/CannyLive/res/values/template-styles.xml b/java/tests/CannyLive/res/values/template-styles.xml
new file mode 100644
index 0000000..777b347
--- /dev/null
+++ b/java/tests/CannyLive/res/values/template-styles.xml
@@ -0,0 +1,35 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+
+<resources>
+
+    <!-- Activity themes -->
+
+    <style name="Theme.Base" parent="android:Theme.Light" />
+
+    <style name="Theme.Sample" parent="Theme.Base" />
+
+    <!-- Widget styling -->
+
+    <style name="Widget" />
+
+    <style name="Widget.SampleMessage">
+        <item name="android:textAppearance">?android:textAppearanceMedium</item>
+        <item name="android:lineSpacingMultiplier">1.1</item>
+    </style>
+
+
+
+</resources>
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/CameraOps.java b/java/tests/CannyLive/src/com/android/example/cannylive/CameraOps.java
new file mode 100644
index 0000000..de119c9
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/CameraOps.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Simple interface for operating the camera, with major camera operations
+ * all performed on a background handler thread.
+ */
+public class CameraOps {
+
+    private static final String TAG = "CameraOps";
+    private static final long ONE_SECOND = 1000000000;
+    public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms
+
+    private final CameraManager mCameraManager;
+    private CameraDevice mCameraDevice;
+    private CameraCaptureSession mCameraSession;
+    private List<Surface> mSurfaces;
+
+    private final ConditionVariable mCloseWaiter = new ConditionVariable();
+
+    private HandlerThread mCameraThread;
+    private Handler mCameraHandler;
+
+    private final ErrorDisplayer mErrorDisplayer;
+
+    private final CameraReadyListener mReadyListener;
+    private final Handler mReadyHandler;
+
+    private int mISOmax;
+    private int mISOmin;
+    private long mExpMax;
+    private long mExpMin;
+    private float mFocusMin;
+    private float mFocusDist = 0;
+    private int mIso;
+    boolean mAutoExposure = true;
+    boolean mAutoFocus = true;
+    private long mExposure = ONE_SECOND / 33;
+
+    private Object mAutoExposureTag = new Object();
+
+    private ImageReader mImageReader;
+    private Handler mBackgroundHandler;
+    private CameraCharacteristics mCameraInfo;
+    private HandlerThread mBackgroundThread;
+    CaptureRequest.Builder mHdrBuilder;
+    private Surface mProcessingNormalSurface;
+    CaptureRequest mPreviewRequest;
+    private String mSaveFileName;
+    private Context mContext;
+    private int mCaptureMode;
+
+    public String resume() {
+        String errorMessage = "Unknown error";
+        boolean foundCamera = false;
+        try {
+            // Find first back-facing camera that has necessary capability
+            String[] cameraIds = mCameraManager.getCameraIdList();
+            for (String id : cameraIds) {
+                CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
+                int facing = info.get(CameraCharacteristics.LENS_FACING);
+
+                int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+                boolean hasFullLevel
+                        = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
+
+                int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+                int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
+                boolean hasManualControl = hasCapability(capabilities,
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
+                boolean hasEnoughCapability = hasManualControl &&
+                        syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
+                Range<Integer> irange;
+                Range<Long> lrange;
+
+                irange = info.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
+                if (irange != null) {
+                    mISOmax = irange.getUpper();
+                    mISOmin = irange.getLower();
+                    lrange = info.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
+                    mExpMax = lrange.getUpper();
+                    mExpMin = lrange.getLower();
+                    mFocusMin = info.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+                } else {
+                    mISOmax = 200;
+                    mISOmin = 100;
+                    mExpMax = 1000;
+                }
+                mFocusDist = mFocusMin;
+                StreamConfigurationMap map = info.get(
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
+                List<Size> sizeList = Arrays.asList(sizes);
+                Collections.sort(sizeList, new Comparator<Size>() {
+                    @Override
+                    public int compare(Size lhs, Size rhs) {
+                        int leftArea = lhs.getHeight() * lhs.getWidth();
+                        int rightArea = lhs.getHeight() * lhs.getWidth();
+                        return Integer.compare(leftArea, rightArea);
+                    }
+                });
+                Size max = sizeList.get(0);
+                int check = 1;
+                Size big = sizeList.get(check);
+                float aspect = 16/9f;
+                Log.v(TAG,"max big "+max.getWidth()+" x "+max.getHeight());
+                for (int i = 0; i < sizeList.size(); i++) {
+                    Size s = sizeList.get(i);
+                    if (s.getHeight() == 720) {
+                        big = s;
+                        break;
+                    }
+                }
+                Log.v(TAG,"BIG wil be "+big.getWidth()+" x "+big.getHeight());
+                mImageReader = ImageReader.newInstance(big.getWidth(), big.getHeight(),
+                        ImageFormat.JPEG, /*maxImages*/2);
+                mImageReader.setOnImageAvailableListener(
+                        mOnImageAvailableListener, mBackgroundHandler);
+
+                if (facing == CameraCharacteristics.LENS_FACING_BACK &&
+                        (hasFullLevel || hasEnoughCapability)) {
+                    // Found suitable camera - get info, open, and set up outputs
+                    mCameraInfo = info;
+                    openCamera(id);
+                    foundCamera = true;
+                    break;
+                }
+            }
+            if (!foundCamera) {
+                errorMessage = "no back camera";
+            }
+        } catch (CameraAccessException e) {
+            errorMessage = e.getMessage();
+        }
+        // startBackgroundThread
+        mBackgroundThread = new HandlerThread("CameraBackground");
+        mBackgroundThread.start();
+        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+        return (foundCamera) ? null : errorMessage;
+    }
+
+
+    private boolean hasCapability(int[] capabilities, int capability) {
+        for (int c : capabilities) {
+            if (c == capability) return true;
+        }
+        return false;
+    }
+
+    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
+            = new ImageReader.OnImageAvailableListener() {
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(),
+                    mSaveFileName, mContext,mCaptureMode));
+        }
+
+    };
+
+    /**
+     * Saves a JPEG {@link android.media.Image} into the specified {@link java.io.File}.
+     */
+    private static class ImageSaver implements Runnable {
+        private final Image mImage;
+        private final String mName;
+        Context mContext;
+        private int mMode;
+
+        public ImageSaver(Image image, String fileName, Context context,int mode) {
+            mImage = image;
+            mName = fileName;
+            mContext = context;
+            mMode = mode;
+        }
+
+        @Override
+        public void run() {
+            Log.v(TAG, "S>> SAVING...");
+            String url = MediaStoreSaver.insertImage(mContext.getContentResolver(),
+                    new MediaStoreSaver.StreamWriter() {
+                        @Override
+                        public void write(OutputStream imageOut) throws IOException {
+                            try {
+                                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
+                                byte[] bytes = new byte[buffer.remaining()];
+                                Log.v(TAG, "S>> size=" + mImage.getWidth() +
+                                        "," + mImage.getHeight());
+                                Log.v(TAG, "S>> bytes " + bytes.length +
+                                        " (" + bytes.length / (1024 * 1024) + "MB");
+                                Log.v(TAG, "S>> bytes out " + bytes.length / mImage.getWidth());
+                                buffer.get(bytes);
+                                imageOut.write(bytes);
+                            } finally {
+                                mImage.close();
+                            }
+                        }
+                    }, mName, "Saved from Simple Camera Demo");
+            ViewfinderProcessor.reProcessImage(mContext, url, mMode);
+        }
+    }
+
+    /**
+     * Create a new camera ops thread.
+     *
+     * @param errorDisplayer listener for displaying error messages
+     * @param readyListener  listener for notifying when camera is ready for requests
+     */
+    CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer,
+              CameraReadyListener readyListener) {
+        mReadyHandler = new Handler(Looper.getMainLooper());
+
+        mCameraThread = new HandlerThread("CameraOpsThread");
+        mCameraThread.start();
+
+        if (manager == null || errorDisplayer == null ||
+                readyListener == null || mReadyHandler == null) {
+            throw new IllegalArgumentException("Need valid displayer, listener, handler");
+        }
+
+        mCameraManager = manager;
+        mErrorDisplayer = errorDisplayer;
+        mReadyListener = readyListener;
+
+    }
+
+    /**
+     * Open the first backfacing camera listed by the camera manager.
+     * Displays a dialog if it cannot open a camera.
+     */
+    public void openCamera(final String cameraId) {
+        mCameraHandler = new Handler(mCameraThread.getLooper());
+
+        mCameraHandler.post(new Runnable() {
+            public void run() {
+                if (mCameraDevice != null) {
+                    throw new IllegalStateException("Camera already open");
+                }
+                try {
+
+                    mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler);
+                } catch (CameraAccessException e) {
+                    String errorMessage = mErrorDisplayer.getErrorString(e);
+                    mErrorDisplayer.showErrorDialog(errorMessage);
+                }
+            }
+        });
+    }
+
+    public void pause() {
+
+        closeCameraAndWait();
+        mBackgroundThread.quitSafely();
+        try {
+            mBackgroundThread.join();
+            mBackgroundThread = null;
+            mBackgroundHandler = null;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Size getBestSize() {
+        // Find a good size for output - largest 16:9 aspect ratio that's less than 720p
+        final int MAX_WIDTH = 640;
+        final float TARGET_ASPECT = 16.f / 9.f;
+
+
+        StreamConfigurationMap configs =
+                mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
+
+        Size outputSize = null;
+        ArrayList<Size> smallEnough = new ArrayList<Size>();
+        for (Size candidateSize : outputSizes) {
+            if (candidateSize.getWidth() <= MAX_WIDTH) {
+                Log.v(TAG, "consider " + candidateSize);
+                smallEnough.add(candidateSize);
+            }
+        }
+        if (smallEnough.size() == 0) {
+            return outputSizes[outputSizes.length - 1]; //pick the smallest
+        }
+        Size maxSize = smallEnough.get(0);
+        double aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
+        for (Size candidateSize : smallEnough) {
+            if (maxSize.getWidth() < candidateSize.getWidth()) {
+                maxSize = candidateSize;
+                aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
+            }
+            if (maxSize.getWidth() == candidateSize.getWidth()) {
+                if (aspectDelta > Math.abs(candidateSize.getWidth() / candidateSize.getHeight() - TARGET_ASPECT)) {
+                    maxSize = candidateSize;
+                    aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
+                }
+            }
+        }
+
+        return maxSize;
+    }
+
+    /**
+     * Close the camera and wait for the close callback to be called in the camera thread.
+     * Times out after @{value CAMERA_CLOSE_TIMEOUT} ms.
+     */
+    public void closeCameraAndWait() {
+        mCloseWaiter.close();
+        mCameraHandler.post(mCloseCameraRunnable);
+        boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT);
+        if (!closed) {
+            Log.e(TAG, "Timeout closing camera");
+        }
+    }
+
+    private Runnable mCloseCameraRunnable = new Runnable() {
+        public void run() {
+            if (mCameraDevice != null) {
+                mCameraDevice.close();
+            }
+            mCameraDevice = null;
+            mCameraSession = null;
+            mSurfaces = null;
+        }
+    };
+
+    /**
+     * Set the output Surfaces, and finish configuration if otherwise ready.
+     */
+    public void setSurface(Surface surface) {
+        final List<Surface> surfaceList = new ArrayList<Surface>();
+        surfaceList.add(surface);
+        surfaceList.add(mImageReader.getSurface());
+
+        mCameraHandler.post(new Runnable() {
+            public void run() {
+                mSurfaces = surfaceList;
+                startCameraSession();
+            }
+        });
+    }
+
+    /**
+     * Get a request builder for the current camera.
+     */
+    public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException {
+        CameraDevice device = mCameraDevice;
+        if (device == null) {
+            throw new IllegalStateException("Can't get requests when no camera is open");
+        }
+        return device.createCaptureRequest(template);
+    }
+
+    /**
+     * Set a repeating request.
+     */
+    public void setRepeatingRequest(final CaptureRequest request,
+                                    final CameraCaptureSession.CaptureCallback listener,
+                                    final Handler handler) {
+        mCameraHandler.post(new Runnable() {
+            public void run() {
+                try {
+                    mCameraSession.setRepeatingRequest(request, listener, handler);
+                } catch (CameraAccessException e) {
+                    String errorMessage = mErrorDisplayer.getErrorString(e);
+                    mErrorDisplayer.showErrorDialog(errorMessage);
+                }
+            }
+        });
+    }
+
+    /**
+     * Set a repeating request.
+     */
+    public void setRepeatingBurst(final List<CaptureRequest> requests,
+                                  final CameraCaptureSession.CaptureCallback listener,
+                                  final Handler handler) {
+        mCameraHandler.post(new Runnable() {
+            public void run() {
+                try {
+                    mCameraSession.setRepeatingBurst(requests, listener, handler);
+
+                } catch (CameraAccessException e) {
+                    String errorMessage = mErrorDisplayer.getErrorString(e);
+                    mErrorDisplayer.showErrorDialog(errorMessage);
+                }
+            }
+        });
+    }
+
+    /**
+     * Configure the camera session.
+     */
+    private void startCameraSession() {
+        // Wait until both the camera device is open and the SurfaceView is ready
+        if (mCameraDevice == null || mSurfaces == null) return;
+
+        try {
+
+            mCameraDevice.createCaptureSession(
+                    mSurfaces, mCameraSessionListener, mCameraHandler);
+        } catch (CameraAccessException e) {
+            String errorMessage = mErrorDisplayer.getErrorString(e);
+            mErrorDisplayer.showErrorDialog(errorMessage);
+            mCameraDevice.close();
+            mCameraDevice = null;
+        }
+    }
+
+    /**
+     * Main listener for camera session events
+     * Invoked on mCameraThread
+     */
+    private CameraCaptureSession.StateCallback mCameraSessionListener =
+            new CameraCaptureSession.StateCallback() {
+
+                @Override
+                public void onConfigured(CameraCaptureSession session) {
+                    mCameraSession = session;
+                    mReadyHandler.post(new Runnable() {
+                        public void run() {
+                            // This can happen when the screen is turned off and turned back on.
+                            if (null == mCameraDevice) {
+                                return;
+                            }
+
+                            mReadyListener.onCameraReady();
+                        }
+                    });
+
+                }
+
+                @Override
+                public void onConfigureFailed(CameraCaptureSession session) {
+                    mErrorDisplayer.showErrorDialog("Unable to configure the capture session");
+                    mCameraDevice.close();
+                    mCameraDevice = null;
+                }
+            };
+
+    /**
+     * Main listener for camera device events.
+     * Invoked on mCameraThread
+     */
+    private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() {
+
+        @Override
+        public void onOpened(CameraDevice camera) {
+            mCameraDevice = camera;
+            startCameraSession();
+        }
+
+        @Override
+        public void onClosed(CameraDevice camera) {
+            mCloseWaiter.open();
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice camera) {
+            mErrorDisplayer.showErrorDialog("The camera device has been disconnected.");
+            camera.close();
+            mCameraDevice = null;
+        }
+
+        @Override
+        public void onError(CameraDevice camera, int error) {
+            mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error);
+            camera.close();
+            mCameraDevice = null;
+        }
+
+    };
+
+    public void captureStillPicture(int currentJpegRotation, String name, Context context, int mode) {
+        mSaveFileName = name;
+        mContext = context;
+        mCaptureMode = mode;
+        try {
+            // TODO call lock focus if we are in "AF-S(One-Shot AF) mode"
+            // TODO call precapture if we are using flash
+            // This is the CaptureRequest.Builder that we use to take a picture.
+            final CaptureRequest.Builder captureBuilder =
+                    createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            Log.v(TAG, "S>>  Target " + mImageReader.getWidth() + "," + mImageReader.getHeight());
+
+            captureBuilder.addTarget(mImageReader.getSurface());
+
+            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, currentJpegRotation);
+
+            CameraCaptureSession.CaptureCallback captureCallback
+                    = new CameraCaptureSession.CaptureCallback() {
+
+                @Override
+                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+                                               TotalCaptureResult result) {
+                    Log.v(TAG, "S>>  onCaptureCompleted");
+                    setParameters();
+                }
+            };
+
+
+            setRequest(captureBuilder.build(), captureCallback, null);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Set a repeating request.
+     */
+    private void setRequest(final CaptureRequest request,
+                            final CameraCaptureSession.CaptureCallback listener,
+                            final Handler handler) {
+        mCameraHandler.post(new Runnable() {
+            public void run() {
+                try {
+                    mCameraSession.stopRepeating();
+                    mCameraSession.capture(request, listener, handler);
+                } catch (CameraAccessException e) {
+                    String errorMessage = mErrorDisplayer.getErrorString(e);
+                    mErrorDisplayer.showErrorDialog(errorMessage);
+                }
+            }
+        });
+    }
+
+    public void setUpCamera(Surface processingNormalSurface) {
+        mProcessingNormalSurface = processingNormalSurface;
+        // Ready to send requests in, so set them up
+        try {
+            CaptureRequest.Builder previewBuilder =
+                    createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            previewBuilder.addTarget(mProcessingNormalSurface);
+            previewBuilder.setTag(mAutoExposureTag);
+            mPreviewRequest = previewBuilder.build();
+            mHdrBuilder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                    CaptureRequest.CONTROL_AE_MODE_OFF);
+            mHdrBuilder.addTarget(mProcessingNormalSurface);
+            setParameters();
+
+        } catch (CameraAccessException e) {
+            String errorMessage = e.getMessage();
+            // MessageDialogFragment.newInstance(errorMessage).show(getFragmentManager(), FRAGMENT_DIALOG);
+        }
+    }
+
+    /**
+     * Start running an HDR burst on a configured camera session
+     */
+    public void setParameters() {
+        if (mHdrBuilder == null) {
+            Log.v(TAG, " Camera not set up");
+            return;
+        }
+        if (mAutoExposure) {
+            mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
+            mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
+            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+        } else {
+            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
+            mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
+            mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
+            mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, getIso());
+        }
+        if (mAutoFocus) {
+            mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
+            mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+        } else {
+            mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
+            mHdrBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, getFocusDistance());
+            mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+        }
+
+        setRepeatingRequest(mHdrBuilder.build(), mCaptureCallback, mReadyHandler);
+    }
+
+    private CameraCaptureSession.CaptureCallback mCaptureCallback
+            = new CameraCaptureSession.CaptureCallback() {
+
+        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+                                       TotalCaptureResult result) {
+        }
+    };
+
+    /**
+     * Simple listener for main code to know the camera is ready for requests, or failed to
+     * start.
+     */
+    public interface CameraReadyListener {
+        public void onCameraReady();
+    }
+
+    /**
+     * Simple listener for displaying error messages
+     */
+    public interface ErrorDisplayer {
+        public void showErrorDialog(String errorMessage);
+
+        public String getErrorString(CameraAccessException e);
+    }
+
+    public float getFocusDistance() {
+        return mFocusDist;
+    }
+
+    public void setFocusDistance(float focusDistance) {
+        mFocusDist = focusDistance;
+    }
+
+    public void setIso(int iso) {
+        mIso = iso;
+    }
+
+    public boolean isAutoExposure() {
+        return mAutoExposure;
+    }
+
+    public void setAutoExposure(boolean autoExposure) {
+        mAutoExposure = autoExposure;
+    }
+
+    public boolean isAutoFocus() {
+        return mAutoFocus;
+    }
+
+    public void setAutoFocus(boolean autoFocus) {
+        mAutoFocus = autoFocus;
+    }
+
+    public int getIso() {
+        return mIso;
+    }
+
+    public long getExposure() {
+        return mExposure;
+    }
+
+    public void setExposure(long exposure) {
+        mExposure = exposure;
+    }
+
+    public int getIsoMax() {
+        return mISOmax;
+    }
+
+    public int getIsoMin() {
+        return mISOmin;
+    }
+
+    public long getExpMax() {
+        return mExpMax;
+    }
+
+    public long getExpMin() {
+        return mExpMin;
+    }
+
+    public float getFocusMin() {
+        return mFocusMin;
+    }
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/CameraView.java b/java/tests/CannyLive/src/com/android/example/cannylive/CameraView.java
new file mode 100644
index 0000000..f65a829
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/CameraView.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.renderscript.RenderScript;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Size;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import com.android.example.cannylive.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by hoford on 2/27/15.
+ */
+public class CameraView extends FixedAspectSurfaceView {
+    private static final String TAG = "CameraPreView";
+
+    private static final long MICRO_SECOND = 1000;
+    private static final long MILLI_SECOND = MICRO_SECOND * 1000;
+    private static final long ONE_SECOND = MILLI_SECOND * 1000;
+
+    private Surface mPreviewSurface;
+    ViewfinderProcessor mProcessor;
+    private Surface mProcessingNormalSurface;
+    CameraOps mCameraOps;
+    CameraManager mCameraManager;
+    Activity mActivity;
+    Context mContext;
+    byte mode = 0;
+    public static final byte MODE_NONE = 0;
+    public static final byte MODE_SPEED = 1;
+    public static final byte MODE_FOCUS = 2;
+    public static final byte MODE_ISO = 3;
+    RenderScript mRS;
+    ErrorCallback mErrorCallback;
+    ParametersChangedCallback mParametersChangedCallback;
+
+    public CameraView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+
+        mRS = RenderScript.create(mContext);
+        SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                mPreviewSurface = holder.getSurface();
+                setupProcessor();
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                mPreviewSurface = null;
+            }
+        };
+        getHolder().addCallback(callback);
+        mCameraManager = (CameraManager) mContext.getSystemService(mContext.CAMERA_SERVICE);
+
+        CameraOps.ErrorDisplayer errorDisplayer = new CameraOps.ErrorDisplayer() {
+
+            @Override
+            public void showErrorDialog(String errorMessage) {
+                Log.v(TAG, "ERROR");
+                if (mErrorCallback != null) {
+                    mErrorCallback.showError(errorMessage);
+                }
+            }
+
+            @Override
+            public String getErrorString(CameraAccessException e) {
+                switch (e.getReason()) {
+                    case CameraAccessException.CAMERA_DISABLED:
+                        return mContext.getString(R.string.camera_disabled);
+                    case CameraAccessException.CAMERA_DISCONNECTED:
+                        return mContext.getString(R.string.camera_disconnected);
+                    case CameraAccessException.CAMERA_ERROR:
+                        return mContext.getString(R.string.camera_error);
+                    default:
+                        return mContext.getString(R.string.camera_unknown, e.getReason());
+
+                }
+            }
+        };
+
+        CameraOps.CameraReadyListener cameraReadyListener = new CameraOps.CameraReadyListener() {
+            @Override
+            public void onCameraReady() {
+                mCameraOps.setUpCamera(mProcessingNormalSurface);
+            }
+        };
+        setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                return touchScreen(event);
+            }
+        });
+        mCameraOps = new CameraOps(mCameraManager,
+                errorDisplayer,
+                cameraReadyListener);
+    }
+
+    public void resume(Activity activity) {
+        mActivity = activity;
+
+        String errorMessage = mCameraOps.resume();
+        if (errorMessage != null) {
+            if (mErrorCallback != null) {
+                mErrorCallback.showError(errorMessage);
+            }
+        } else {
+
+            Size outputSize = mCameraOps.getBestSize();
+            mProcessor = new ViewfinderProcessor(mRS, outputSize);
+            // Configure the output view - this will fire surfaceChanged
+            setAspectRatio((float) outputSize.getWidth() / outputSize.getHeight());
+            getHolder().setFixedSize(outputSize.getWidth(), outputSize.getHeight());
+        }
+    }
+
+    public void pause() {
+        mProcessor.close();
+        mCameraOps.pause();
+    }
+
+    /**
+     * Once camera is open and output surfaces are ready, configure the RS processing
+     * and the camera device inputs/outputs.
+     */
+    private void setupProcessor() {
+        if (mProcessor == null || mPreviewSurface == null) return;
+        mProcessor.setOutputSurface(mPreviewSurface);
+        mProcessingNormalSurface = mProcessor.getInputSurface();
+        mCameraOps.setSurface(mProcessingNormalSurface);
+    }
+
+    public void takePicture(int mode) {
+        // Orientation
+        Log.v(TAG,"Taking picture");
+        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+        int jpegRotation = Surface.ROTATION_0;
+        switch (rotation) {
+            case 90:
+                jpegRotation = Surface.ROTATION_0;
+                break;
+            case 0:
+                jpegRotation = Surface.ROTATION_90;
+                break;
+            case 180:
+                jpegRotation = Surface.ROTATION_270;
+                break;
+            case 270:
+                jpegRotation = Surface.ROTATION_180;
+                break;
+        }
+        String name = "Simple" + System.currentTimeMillis() + ".jpg";
+        mCameraOps.captureStillPicture(jpegRotation, name, mContext, mode);
+    }
+
+    private CameraCaptureSession.CaptureCallback mPhotoCallback
+            = new CameraCaptureSession.CaptureCallback() {
+
+        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+                                       TotalCaptureResult result) {
+            Log.v(TAG, "onCaptureCompleted " + result.toString());
+        }
+    };
+
+    float mDownY;
+    long mExposureDown;
+    float mFocusDistDown;
+
+    public boolean touchScreen(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mDownY = event.getY();
+            mExposureDown = mCameraOps.getExposure();
+            mFocusDistDown = mCameraOps.getFocusDistance();
+            if (mFocusDistDown == 0.0) {
+                mFocusDistDown = 0.01f;
+            }
+        }
+        float distanceY = event.getY() - mDownY;
+        float width = getWidth();
+        float height = getHeight();
+
+        float yDistNorm = distanceY / height;
+
+        float ACCELERATION_FACTOR = 8;
+        float scaleFactor = (float) Math.pow(2.f, yDistNorm * ACCELERATION_FACTOR);
+
+        switch (mode) {
+            case MODE_SPEED:
+                long exp = (long) (mExposureDown * scaleFactor);
+                exp = Math.min(mCameraOps.getExpMax(), exp);
+                mCameraOps.setExposure(Math.max(mCameraOps.getExpMin(), exp));
+                Log.v(TAG, "mExposure =" + mCameraOps.getExposure());
+                break;
+            case MODE_FOCUS:
+                float focusDist = mFocusDistDown * scaleFactor;
+                focusDist = Math.max(0.0f, Math.min(mCameraOps.getFocusMin(), focusDist));
+                if (focusDist < 0.01) focusDist = 0;
+                mCameraOps.setFocusDistance(focusDist);
+                Log.v(TAG, "mFocusDist =" + focusDist);
+                break;
+            case MODE_ISO:
+                ACCELERATION_FACTOR = 2;
+                scaleFactor = (float) Math.pow(2.f, yDistNorm * ACCELERATION_FACTOR);
+                int iso = (int) (getIso() * scaleFactor);
+                iso = Math.min(mCameraOps.getIsoMax(), iso);
+                mCameraOps.setIso(Math.max(mCameraOps.getIsoMin(), iso));
+                break;
+        }
+
+        if (mParametersChangedCallback != null) {
+            mParametersChangedCallback.parametersChanged();
+        }
+        mCameraOps.setParameters();
+
+        return true;
+    }
+
+    public void setMode(byte mode) {
+        this.mode = mode;
+    }
+
+    public byte getMode() {
+        return mode;
+    }
+
+    public int getIso() {
+        return mCameraOps.getIso();
+    }
+
+    public void setIso(int iso) {
+        mCameraOps.setIso(iso);
+        if (mParametersChangedCallback != null) {
+            mParametersChangedCallback.parametersChanged();
+        }
+        mCameraOps.setParameters();
+    }
+
+    public long getExposure() {
+        return mCameraOps.getExposure();
+    }
+
+    public void setExposure(long exposure) {
+        mCameraOps.setExposure(exposure);
+        if (mParametersChangedCallback != null) {
+            mParametersChangedCallback.parametersChanged();
+        }
+        mCameraOps.setParameters();
+    }
+
+    public float getFocusDist() {
+        return mCameraOps.getFocusDistance();
+    }
+
+    public void setFocusInMeters(float dist) {
+        float min = mCameraOps.getFocusMin();
+        float d = 10 / (dist + 10 / min);
+        setFocusDist(d);
+    }
+
+    public void setFocusDist(float dist) {
+        mCameraOps.setFocusDistance(dist);
+        mCameraOps.setParameters();
+    }
+    public void changeEffectMode() {
+        mProcessor.changeEffectMode();
+    }
+    public float getMinFocusDistance() {
+        return mCameraOps.getFocusMin();
+    }
+
+    public void setAutofocus(boolean autofocus) {
+        mCameraOps.setAutoFocus(autofocus);
+        mCameraOps.setParameters();
+    }
+
+    public boolean isAutoExposure() {
+        return mCameraOps.isAutoExposure();
+    }
+
+    public boolean isAutofocus() {
+        return mCameraOps.isAutoFocus();
+    }
+
+    public void setAutoExposure(boolean autoExposure) {
+        mCameraOps.setAutoExposure(autoExposure);
+        mCameraOps.setParameters();
+    }
+
+    public int getEffect() {
+        return mProcessor.getMode();
+    }
+
+
+    public static interface ErrorCallback {
+        public void showError(String errorMessage);
+    }
+
+    public void setErrorCallback(ErrorCallback errorCallback) {
+        mErrorCallback = errorCallback;
+    }
+
+    public static interface ParametersChangedCallback {
+        public void parametersChanged();
+    }
+
+    public void setParametersChangedCallback(ParametersChangedCallback parametersChangedCallback) {
+        mParametersChangedCallback = parametersChangedCallback;
+    }
+
+    float getFps() {
+        if (mProcessor==null) {
+            return 0.0f;
+        }
+        return mProcessor.getmFps();
+    }
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/FixedAspectSurfaceView.java b/java/tests/CannyLive/src/com/android/example/cannylive/FixedAspectSurfaceView.java
new file mode 100644
index 0000000..180b65e
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/FixedAspectSurfaceView.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+import com.android.example.cannylive.R;
+
+/**
+ * A SurfaceView that maintains its aspect ratio to be a desired target value.
+ * <p/>
+ * <p>Depending on the layout, the FixedAspectSurfaceView may not be able to maintain the
+ * requested aspect ratio. This can happen if both the width and the height are exactly
+ * determined by the layout.  To avoid this, ensure that either the height or the width is
+ * adjustable by the view; for example, by setting the layout parameters to be WRAP_CONTENT for
+ * the dimension that is best adjusted to maintain the aspect ratio.</p>
+ */
+public class FixedAspectSurfaceView extends SurfaceView {
+
+    /**
+     * Desired width/height ratio
+     */
+    private float mAspectRatio;
+
+    private GestureDetector mGestureDetector;
+
+    public FixedAspectSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Get initial aspect ratio from custom attributes
+        TypedArray a =
+                context.getTheme().obtainStyledAttributes(attrs,
+                        R.styleable.FixedAspectSurfaceView, 0, 0);
+        setAspectRatio(a.getFloat(
+                R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f));
+        a.recycle();
+    }
+
+    /**
+     * Set the desired aspect ratio for this view.
+     *
+     * @param aspect the desired width/height ratio in the current UI orientation. Must be a
+     *               positive value.
+     */
+    public void setAspectRatio(float aspect) {
+        if (aspect <= 0) {
+            throw new IllegalArgumentException("Aspect ratio must be positive");
+        }
+        mAspectRatio = aspect;
+        requestLayout();
+    }
+
+    /**
+     * Set a gesture listener to listen for touch events
+     */
+    public void setGestureListener(Context context, GestureDetector.OnGestureListener listener) {
+        if (listener == null) {
+            mGestureDetector = null;
+        } else {
+            mGestureDetector = new GestureDetector(context, listener);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+
+        // General goal: Adjust dimensions to maintain the requested aspect ratio as much
+        // as possible. Depending on the measure specs handed down, this may not be possible
+
+        // Only set one of these to true
+        boolean scaleWidth = false;
+        boolean scaleHeight = false;
+
+        // Sort out which dimension to scale, if either can be. There are 9 combinations of
+        // possible measure specs; a few cases below handle multiple combinations
+        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
+            // Can't adjust sizes at all, do nothing
+        } else if (widthMode == MeasureSpec.EXACTLY) {
+            // Width is fixed, heightMode either AT_MOST or UNSPECIFIED, so adjust height
+            scaleHeight = true;
+        } else if (heightMode == MeasureSpec.EXACTLY) {
+            // Height is fixed, widthMode either AT_MOST or UNSPECIFIED, so adjust width
+            scaleWidth = true;
+        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
+            // Need to fit into box <= [width, height] in size.
+            // Maximize the View's area while maintaining aspect ratio
+            // This means keeping one dimension as large as possible and shrinking the other
+            float boxAspectRatio = width / (float) height;
+            if (boxAspectRatio > mAspectRatio) {
+                // Box is wider than requested aspect; pillarbox
+                scaleWidth = true;
+            } else {
+                // Box is narrower than requested aspect; letterbox
+                scaleHeight = true;
+            }
+        } else if (widthMode == MeasureSpec.AT_MOST) {
+            // Maximize width, heightSpec is UNSPECIFIED
+            scaleHeight = true;
+        } else if (heightMode == MeasureSpec.AT_MOST) {
+            // Maximize height, widthSpec is UNSPECIFIED
+            scaleWidth = true;
+        } else {
+            // Both MeasureSpecs are UNSPECIFIED. This is probably a pathological layout,
+            // with width == height == 0
+            // but arbitrarily scale height anyway
+            scaleHeight = true;
+        }
+
+        // Do the scaling
+        if (scaleWidth) {
+            width = (int) (height * mAspectRatio);
+        } else if (scaleHeight) {
+            height = (int) (width / mAspectRatio);
+        }
+
+        // Override width/height if needed for EXACTLY and AT_MOST specs
+        width = View.resolveSizeAndState(width, widthMeasureSpec, 0);
+        height = View.resolveSizeAndState(height, heightMeasureSpec, 0);
+
+        // Finally set the calculated dimensions
+        setMeasuredDimension(width, height);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureDetector != null) {
+            return mGestureDetector.onTouchEvent(event);
+        }
+        return false;
+    }
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/MainActivity.java b/java/tests/CannyLive/src/com/android/example/cannylive/MainActivity.java
new file mode 100644
index 0000000..75d8d47
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/MainActivity.java
@@ -0,0 +1,185 @@
+/*

+ * Copyright (C) 2015 The Android Open Source Project

+ *

+ * 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 com.android.example.cannylive;

+

+import android.Manifest;

+import android.app.Activity;

+import android.content.pm.ActivityInfo;

+import android.content.pm.PackageManager;

+import android.os.Bundle;

+import android.support.v4.app.ActivityCompat;

+import android.support.v4.content.ContextCompat;

+import android.util.Log;

+import android.view.View;

+import android.widget.Button;

+import android.widget.SeekBar;

+import android.widget.TextView;

+import android.widget.Toast;

+import android.widget.ViewFlipper;

+

+import com.android.example.cannylive.R;

+

+import java.text.DecimalFormat;

+import java.util.Timer;

+import java.util.TimerTask;

+

+/**

+ * Main Activity for this app

+ * It presents a ui for setting ISO, Shutter speed, and focus

+ */

+public class MainActivity extends Activity {

+    private static final String TAG = "MainActivity";

+    private static final long ONE_SECOND = 1000000000;

+    private CameraView mPreviewView;

+

+

+    private Timer mTimer;

+

+    @Override

+    protected void onCreate(Bundle savedInstanceState) {

+        super.onCreate(savedInstanceState);

+        setContentView(new TextView(this));

+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

+

+        checkPermission();

+    }

+    protected void onPermissionOK() {

+

+        setContentView(R.layout.activity_main);

+        mPreviewView = (CameraView) findViewById(R.id.preview);

+

+        mTimer = new Timer();

+

+        mTimer.scheduleAtFixedRate(new TimerTask() {

+

+            @Override

+            public void run() {

+

+                runOnUiThread(new Runnable() {

+                    DecimalFormat df = new DecimalFormat("##.##");

+                    public void run() {

+                        setTitle("RS Camera (" + df.format( mPreviewView.getFps()) + "fps)");

+                    }

+                });

+

+            }

+        }, 250, 250);

+    }

+

+    public static final int PERMISSION_CHECK_ID =  22;

+    private void  checkPermission() {

+        String []permission = {

+                Manifest.permission.CAMERA,

+                Manifest.permission.RECORD_AUDIO,

+                Manifest.permission.WRITE_EXTERNAL_STORAGE,

+                Manifest.permission.READ_EXTERNAL_STORAGE

+        };

+        boolean permission_ok = true;

+        boolean []permissionDenied = new boolean[permission.length];

+        for (int i = 0; i < permission.length; i++) {

+            String per = permission[i];

+            int permissionCheck = ContextCompat.checkSelfPermission(this,

+                    permission[i] );

+            permissionDenied[i] = permissionCheck != PackageManager.PERMISSION_GRANTED;

+            if (permissionDenied[i]) {

+                permission_ok = false;

+            }

+        }

+        Log.v(TAG, "Permission ok = " + permission_ok);

+

+        if (! permission_ok ) {

+            boolean request = false;

+            for (int i = 0; i < permissionDenied.length; i++) {

+                if ( permissionDenied[i]);

+                if (ActivityCompat.shouldShowRequestPermissionRationale(this,

+                        permission[i])) {

+                    request = true;

+                }

+            }

+            if (request) {

+                Toast.makeText(this, "We need this permission", Toast.LENGTH_LONG).show();

+                finish();

+            } else {

+                ActivityCompat.requestPermissions(this,

+                        permission,

+                        PERMISSION_CHECK_ID);

+

+            }

+        } else {

+            onPermissionOK();

+        }

+    }

+    public void onRequestPermissionsResult(int requestCode,

+                                           String permissions[], int[] grantResults) {

+        switch (requestCode) {

+            case PERMISSION_CHECK_ID: {

+                // If request is cancelled, the result arrays are empty.

+                if (grantResults.length > 0

+                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

+

+                    onPermissionOK();

+

+                } else {

+                    Toast.makeText(this, "We need permissions", Toast.LENGTH_LONG).show();

+

+                    finish();

+                }

+                return;

+            }

+

+            // other 'case' lines to check for other

+            // permissions this app might request

+        }

+    }

+    @Override

+    protected void onResume() {

+        super.onResume();

+        if (mPreviewView != null) {

+            mPreviewView.resume(this);

+        }

+    }

+

+    public void changeEffectMode(View v) {

+        mPreviewView.changeEffectMode();

+    }

+

+    @Override

+    protected void onPause() {

+        Log.v(TAG,">>>>>>>>>>>>>> onPause()");

+        super.onPause();

+        if (mPreviewView != null) {

+            mPreviewView.pause();

+        }

+    }

+

+    @Override

+    protected void onStop() {

+        Log.v(TAG,">>>>>>>>>>>>>> onStop()");

+        super.onStop();

+    }

+

+    @Override

+    protected void onDestroy() {

+        Log.v(TAG,">>>>>>>>>>>>>> onDestroy()");

+

+        super.onDestroy();

+    }

+

+    public void capture(View v) {

+        mPreviewView.takePicture(mPreviewView.getEffect());

+    }

+

+}

diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/MediaStoreSaver.java b/java/tests/CannyLive/src/com/android/example/cannylive/MediaStoreSaver.java
new file mode 100644
index 0000000..ab53417
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/MediaStoreSaver.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+
+/**
+ * Utility class to save images into android image database
+ */
+public class MediaStoreSaver {
+    public static interface StreamWriter {
+        void write(OutputStream imageOut) throws IOException;
+    }
+
+    public static final String insertImage(ContentResolver contentResolver,
+                                           Bitmap image,
+                                           String title,
+                                           String description) {
+        final Bitmap source = image;
+        StreamWriter streamWriter = new StreamWriter() {
+            public void write(OutputStream imageOut) {
+                source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
+            }
+        };
+        return insertImage(contentResolver, streamWriter, title, description);
+    }
+
+
+    public static final String insertImage(ContentResolver cr,
+                                           StreamWriter source,
+                                           String title,
+                                           String description) {
+
+        ContentValues values = new ContentValues();
+        values.put(Images.Media.TITLE, title);
+        values.put(Images.Media.DISPLAY_NAME, title);
+        values.put(Images.Media.DESCRIPTION, description);
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        // Add the date meta data to ensure the image is added at the front of the gallery
+        values.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
+        values.put(Images.Media.DATE_TAKEN, System.currentTimeMillis());
+
+        Uri url = null;
+        String stringUrl = null;    /* value to be returned */
+
+        try {
+            url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
+            if (source != null) {
+                OutputStream imageOut = cr.openOutputStream(url);
+                try {
+                    source.write(imageOut);
+                } finally {
+                    imageOut.close();
+                }
+
+                long id = ContentUris.parseId(url);
+                // Wait until MINI_KIND thumbnail is generated.
+                Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MINI_KIND, null);
+                // This is for backward compatibility.
+                storeThumbnail(cr, miniThumb, id, 50F, 50F, Images.Thumbnails.MICRO_KIND);
+            } else {
+                cr.delete(url, null, null);
+                url = null;
+            }
+        } catch (Exception e) {
+            if (url != null) {
+                cr.delete(url, null, null);
+                url = null;
+            }
+        }
+
+        if (url != null) {
+            stringUrl = url.toString();
+        }
+
+        return stringUrl;
+    }
+
+    /**
+     * A copy of the Android internals StoreThumbnail method, it used with the insertImage to
+     * populate the android.provider.MediaStore.Images.Media#insertImage with all the correct
+     * meta data. The StoreThumbnail method is private so it must be duplicated here.
+     *
+     * @see android.provider.MediaStore.Images.Media (StoreThumbnail private method)
+     */
+    private static final Bitmap storeThumbnail(
+            ContentResolver cr,
+            Bitmap source,
+            long id,
+            float width,
+            float height,
+            int kind) {
+
+        // create the matrix to scale it
+        Matrix matrix = new Matrix();
+
+        float scaleX = width / source.getWidth();
+        float scaleY = height / source.getHeight();
+
+        matrix.setScale(scaleX, scaleY);
+
+        Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
+                source.getWidth(),
+                source.getHeight(), matrix,
+                true
+        );
+
+        ContentValues values = new ContentValues(4);
+        values.put(Images.Thumbnails.KIND, kind);
+        values.put(Images.Thumbnails.IMAGE_ID, (int) id);
+        values.put(Images.Thumbnails.HEIGHT, thumb.getHeight());
+        values.put(Images.Thumbnails.WIDTH, thumb.getWidth());
+
+        Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
+
+        try {
+            OutputStream thumbOut = cr.openOutputStream(url);
+            thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
+            thumbOut.close();
+            return thumb;
+        } catch (FileNotFoundException ex) {
+            return null;
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/VerticalSeekBar.java b/java/tests/CannyLive/src/com/android/example/cannylive/VerticalSeekBar.java
new file mode 100644
index 0000000..33c4ddf
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/VerticalSeekBar.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.SeekBar;
+
+/**
+ * Class to create a vertical slider
+ */
+public class VerticalSeekBar extends SeekBar {
+
+    public VerticalSeekBar(Context context) {
+        super(context);
+    }
+
+    public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public VerticalSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(h, w, oldh, oldw);
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(heightMeasureSpec, widthMeasureSpec);
+        setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
+    }
+
+    protected void onDraw(Canvas c) {
+        c.rotate(-90);
+        c.translate(-getHeight(), 0);
+
+        super.onDraw(c);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!isEnabled()) {
+            return false;
+        }
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_UP:
+                setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
+                onSizeChanged(getWidth(), getHeight(), 0, 0);
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                break;
+        }
+        return true;
+    }
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/ViewfinderProcessor.java b/java/tests/CannyLive/src/com/android/example/cannylive/ViewfinderProcessor.java
new file mode 100644
index 0000000..88cedc7
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/ViewfinderProcessor.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.example.cannylive;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Script;
+import android.renderscript.Type;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.FileNotFoundException;
+import java.text.DecimalFormat;
+
+/**
+ * Renderscript-based Focus peaking viewfinder
+ */
+public class ViewfinderProcessor {
+    private static final String TAG = "ViewfinderProcessor";
+    int mCount;
+    long mLastTime;
+    float mFps;
+    RenderScript mRs;
+    private Allocation mInputAllocation;
+    private Allocation mOutputAllocation;
+    private Allocation mBlurAllocation;
+    private Allocation mEdgeAllocation;
+    private HandlerThread mProcessingThread;
+    private Handler mProcessingHandler;
+    private ScriptC_canny mScriptCanny;
+    public ProcessingTask mProcessingTask;
+    public Allocation mHoughOutput;
+    public Allocation mHoughSlices;
+    private volatile int mMode = 1;
+    DecimalFormat df = new DecimalFormat("###.##");
+
+    public ViewfinderProcessor(RenderScript rs, Size dimensions) {
+        mRs = rs;
+        Type.Builder yuvTypeBuilder = new Type.Builder(rs, Element.YUV(rs));
+        yuvTypeBuilder.setX(dimensions.getWidth());
+        yuvTypeBuilder.setY(dimensions.getHeight());
+        yuvTypeBuilder.setYuvFormat(ImageFormat.YUV_420_888);
+        Log.d(TAG, ">>>>>>>>>>>>  " + dimensions.getWidth() + "x" + dimensions.getHeight());
+        mInputAllocation = Allocation.createTyped(rs, yuvTypeBuilder.create(),
+                Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT);
+
+        Type.Builder rgbTypeBuilder = new Type.Builder(rs, Element.RGBA_8888(rs));
+        rgbTypeBuilder.setX(dimensions.getWidth());
+        rgbTypeBuilder.setY(dimensions.getHeight());
+
+        mOutputAllocation = Allocation.createTyped(rs, rgbTypeBuilder.create(),
+                Allocation.USAGE_IO_OUTPUT | Allocation.USAGE_SCRIPT);
+        Type.Builder buffTypeBuilder = new Type.Builder(rs, Element.U8(rs));
+        buffTypeBuilder.setX(dimensions.getWidth());
+        buffTypeBuilder.setY(dimensions.getHeight());
+        mBlurAllocation = Allocation.createTyped(rs, buffTypeBuilder.create());
+        mEdgeAllocation = Allocation.createTyped(rs, buffTypeBuilder.create());
+
+        mProcessingThread = new HandlerThread("ViewfinderProcessor");
+        mProcessingThread.start();
+        mProcessingHandler = new Handler(mProcessingThread.getLooper());
+        mScriptCanny = new ScriptC_canny(rs);
+        mScriptCanny.set_blurImage(mBlurAllocation);
+        mScriptCanny.set_edgeImage(mEdgeAllocation);
+        mProcessingTask = new ProcessingTask(mInputAllocation);
+
+        int NO_OF_SLICES = 8;
+        int[] slices = new int[NO_OF_SLICES * 2];
+        for (int i = 0; i < NO_OF_SLICES; i++) {
+            int s1 = i * 360 / NO_OF_SLICES;
+            int s2 = ((1 + i) * 360) / NO_OF_SLICES;
+            slices[i * 2] = s1;
+            slices[i * 2 + 1] = s2;
+        }
+        Type.Builder houghSliceBuilder = new Type.Builder(rs, Element.I32_2(rs));
+        houghSliceBuilder.setX(NO_OF_SLICES);
+        mHoughSlices = Allocation.createTyped(rs, houghSliceBuilder.create(), Allocation.USAGE_SCRIPT);
+        mHoughSlices.copyFrom(slices);
+        Type.Builder houghOutputBuilder = new Type.Builder(rs, Element.U8(rs));
+        houghOutputBuilder.setX(800);
+        houghOutputBuilder.setY(360);
+        mHoughOutput = Allocation.createTyped(rs, houghOutputBuilder.create());
+        mScriptCanny.set_hough_output(mHoughOutput);
+
+    }
+
+    public Surface getInputSurface() {
+        return mInputAllocation.getSurface();
+    }
+
+    public void setOutputSurface(Surface output) {
+        mOutputAllocation.setSurface(output);
+    }
+
+    public float getmFps() {
+        return mFps;
+    }
+
+    public void changeEffectMode() {
+        mMode++;
+    }
+
+    public int getMode() {
+        return mMode;
+    }
+
+    volatile boolean mStop = false;
+
+    public void close() {
+
+        mStop = true;
+    }
+
+    /**
+     * Class to process buffer from camera and output to buffer to screen
+     */
+    class ProcessingTask implements Runnable, Allocation.OnBufferAvailableListener {
+        private int mPendingFrames = 0;
+        int mode = -1;
+        private Allocation mInputAllocation;
+
+        public ProcessingTask(Allocation input) {
+            mInputAllocation = input;
+            mInputAllocation.setOnBufferAvailableListener(this);
+        }
+
+        @Override
+        public void onBufferAvailable(Allocation a) {
+            if (mStop) {
+
+                return;
+            }
+            synchronized (this) {
+                mPendingFrames++;
+                mProcessingHandler.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            // Find out how many frames have arrived
+            int pendingFrames;
+            synchronized (this) {
+                pendingFrames = mPendingFrames;
+                mPendingFrames = 0;
+
+                // Discard extra messages in case processing is slower than frame rate
+                mProcessingHandler.removeCallbacks(this);
+            }
+            if (mInputAllocation == null) return;
+            // Get to newest input
+            for (int i = 0; i < pendingFrames; i++) {
+                mInputAllocation.ioReceive();
+            }
+            mCount++;
+            mScriptCanny.set_gCurrentFrame(mInputAllocation);
+            long time = System.currentTimeMillis() - mLastTime;
+            if (time > 1000) {
+                mLastTime += time;
+                mFps = mCount * 1000 / (float) (time);
+                mCount = 0;
+            }
+            // Run processing pass
+            mScriptCanny.forEach_getyuv_y(mEdgeAllocation);
+
+            Script.LaunchOptions opt = new Script.LaunchOptions();
+            opt.setX(2, mBlurAllocation.getType().getX() - 2);
+            opt.setY(2, mBlurAllocation.getType().getY() - 2);
+            mScriptCanny.forEach_blur_uchar(mBlurAllocation, opt);
+
+            opt.setX(3, mBlurAllocation.getType().getX() - 3);
+            opt.setY(3, mBlurAllocation.getType().getY() - 3);
+            mScriptCanny.forEach_edge(mEdgeAllocation, opt);
+
+            opt.setX(4, mBlurAllocation.getType().getX() - 4);
+            opt.setY(4, mBlurAllocation.getType().getY() - 4);
+            mScriptCanny.forEach_thin(mBlurAllocation, opt);
+
+            opt.setX(5, mBlurAllocation.getType().getX() - 5);
+            opt.setY(5, mBlurAllocation.getType().getY() - 5);
+            mScriptCanny.forEach_hysteresis(mBlurAllocation, mEdgeAllocation, opt);
+
+            switch (mMode % 6) {
+                case 0:
+                default:
+                    long mt = System.nanoTime();
+                    mScriptCanny.forEach_black_uchar(mHoughOutput);
+                    mScriptCanny.forEach_hough(mHoughSlices);
+                    mRs.finish();
+                    mt = System.nanoTime() - mt;
+                    Log.v(TAG, " hough = " + df.format(mt * 1E-6) + "ms");
+                    mScriptCanny.forEach_hough_map(mOutputAllocation);
+                    break;
+                case 1:
+                    mScriptCanny.forEach_toRGB(mOutputAllocation, opt);
+                    break;
+                case 2:
+                    mScriptCanny.forEach_toRGBfuzz(mOutputAllocation, opt);
+                    break;
+                case 3:
+                    mScriptCanny.forEach_toWhiteRGBfuzz(mOutputAllocation, opt);
+                    break;
+                case 4:
+                    mScriptCanny.forEach_toWhiteRGB(mOutputAllocation, opt);
+                    break;
+                case 5:
+                    mScriptCanny.forEach_toCartoon(mOutputAllocation, opt);
+                    break;
+            }
+            mOutputAllocation.ioSend();
+            if (mStop) {
+                if (mInputAllocation != null) {
+                    mInputAllocation.destroy();
+                    mInputAllocation = null;
+                }
+                return;
+            }
+        }
+    }
+
+
+    public static void reProcessImage(Context context, String urlName, int type) {
+
+        ContentResolver cr = context.getContentResolver();
+        try {
+            Uri uri = Uri.parse(urlName);
+            Bitmap b = BitmapFactory.decodeStream(cr.openInputStream(uri));
+            processImage(b, context, type);
+
+            MediaStoreSaver.insertImage(cr, b, "canny", "canny filtered image");
+        } catch (FileNotFoundException e) {
+            Log.v(TAG, "S>> Could not open file ");
+        }
+
+    }
+
+    public static void processImage(Bitmap image, Context context, int mMode) {
+        RenderScript mRs = RenderScript.create(context);
+        int width = image.getWidth();
+        int height = image.getHeight();
+        Allocation img_alloc, blur_alloc, edge_alloc;
+        long time = System.nanoTime();
+        img_alloc = Allocation.createFromBitmap(mRs, image);
+
+        Type.Builder buffTypeBuilder = new Type.Builder(mRs, Element.U8(mRs));
+        buffTypeBuilder.setX(width).setY(height);
+        blur_alloc = Allocation.createTyped(mRs, buffTypeBuilder.create());
+        edge_alloc = Allocation.createTyped(mRs, buffTypeBuilder.create());
+
+
+        ScriptC_canny canny_script = new ScriptC_canny(mRs);
+        canny_script.set_blurImage(blur_alloc);
+        canny_script.set_edgeImage(edge_alloc);
+        canny_script.forEach_getLum(img_alloc, edge_alloc);
+
+        Script.LaunchOptions opt = new Script.LaunchOptions();
+        opt.setX(2, blur_alloc.getType().getX() - 2);
+        opt.setY(2, blur_alloc.getType().getY() - 2);
+        canny_script.forEach_blur_uchar(blur_alloc, opt);
+
+        opt.setX(3, blur_alloc.getType().getX() - 3);
+        opt.setY(3, blur_alloc.getType().getY() - 3);
+        canny_script.forEach_edge(edge_alloc, opt);
+
+        opt.setX(4, blur_alloc.getType().getX() - 4);
+        opt.setY(4, blur_alloc.getType().getY() - 4);
+        canny_script.forEach_thin(blur_alloc, opt);
+
+        opt.setX(5, blur_alloc.getType().getX() - 5);
+        opt.setY(5, blur_alloc.getType().getY() - 5);
+
+        canny_script.forEach_hysteresis(blur_alloc, edge_alloc, opt);
+        switch (mMode % 6) {
+            case 0:
+            case 1:
+            default:
+                canny_script.forEach_toRGB(img_alloc, opt);
+                break;
+            case 2:
+                canny_script.forEach_toRGBfuzz(img_alloc, opt);
+                break;
+            case 3:
+                canny_script.forEach_toWhiteRGBfuzz(img_alloc, opt);
+                break;
+            case 4:
+                canny_script.forEach_toWhiteRGB(img_alloc, opt);
+                break;
+            case 5:
+                canny_script.forEach_toRGBCartoon(img_alloc, img_alloc, opt);
+                break;
+        }
+        img_alloc.copyTo(image);
+        time = System.nanoTime() - time;
+        DecimalFormat df = new DecimalFormat("###.#");
+        String ts = df.format(time * 1E-6) + "ms";
+        Log.v(TAG, "processed a " + width + "x" + height + " in " + ts);
+    }
+
+}
diff --git a/java/tests/CannyLive/src/com/android/example/cannylive/canny.rs b/java/tests/CannyLive/src/com/android/example/cannylive/canny.rs
new file mode 100644
index 0000000..82b14f1
--- /dev/null
+++ b/java/tests/CannyLive/src/com/android/example/cannylive/canny.rs
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.example.cannylive)
+#pragma rs_fp_relaxed
+
+rs_allocation gCurrentFrame;     // yuv
+rs_allocation gCurrentRGBFrame;  // RGB
+rs_allocation blurImage;         // uchar
+rs_allocation edgeImage;         // uchar
+
+ushort __attribute__((kernel)) black_ushort() { return 0; }
+uchar __attribute__((kernel)) black_uchar() { return 0; }
+uchar4 __attribute__((kernel)) black_uchar4() { return 0; }
+
+uchar4 __attribute__((kernel)) toRGB(uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(edgeImage, x, y);
+
+  if (true) {
+    uchar charv = clamp(v * 10, 0, 255);
+    uchar4 out = {charv, charv, charv, 255};
+    return out;
+  }
+}
+uchar4 __attribute__((kernel)) toWhiteRGB(uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(edgeImage, x, y);
+
+  uchar charv = 255 - clamp(v * 10, 0, 255);
+  uchar4 out = {charv, charv, charv, 255};
+  return out;
+}
+uchar4 __attribute__((kernel)) toRGBfuzz(uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(blurImage, x, y);
+
+  if (true) {
+    uchar charv = clamp(v * 10, 0, 255);
+    uchar4 out = {charv, charv, charv, 255};
+    return out;
+  }
+}
+uchar4 __attribute__((kernel)) toWhiteRGBfuzz(uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(blurImage, x, y);
+
+  uchar charv = 255 - clamp(v * 10, 0, 255);
+  uchar4 out = {charv, charv, charv, 255};
+  return out;
+}
+
+uchar4 __attribute__((kernel)) toRGBCartoon(uchar4 in, uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(blurImage, x, y);
+
+  return in - (uchar)(v * 10);
+}
+
+uchar4 __attribute__((kernel)) toCartoon(uint32_t x, uint32_t y) {
+  ushort v = rsGetElementAt_uchar(blurImage, x, y);
+
+  uchar4 yuv;
+  yuv.r =
+      clamp(rsGetElementAtYuv_uchar_Y(gCurrentFrame, x, y) - v * 10, 0, 255);
+
+  yuv.g = rsGetElementAtYuv_uchar_U(gCurrentFrame, x, y);
+  yuv.b = rsGetElementAtYuv_uchar_V(gCurrentFrame, x, y);
+  yuv.a = 255;
+
+  int4 rgb;
+  rgb.r = yuv.r + yuv.b * 1436 / 1024 - 179;
+  rgb.g = yuv.r - yuv.g * 46549 / 131072 + 44 - yuv.b * 93604 / 131072 + 91;
+  rgb.b = yuv.r + yuv.g * 1814 / 1024 - 227;
+  rgb.a = 255;
+
+  // Write out merged HDR result
+  uchar4 out = convert_uchar4(clamp(rgb, 0, 255));
+  return out;
+}
+
+static ushort getY(rs_allocation a, int x, int y) {
+  ushort3 v = convert_ushort3(rsGetElementAt_uchar4(a, x, y).xyz);
+  return v.x + v.y + v.z;
+}
+
+uchar __attribute__((kernel)) getLum(uchar4 in) {
+  return (in.x + in.y + in.z) / 3;
+}
+
+uchar __attribute__((kernel)) getyuv_y(uint32_t x, uint32_t y) {
+  return rsGetElementAtYuv_uchar_Y(gCurrentFrame, x, y);
+}
+
+uchar __attribute__((kernel)) blur_uchar(uint32_t x, uint32_t y) {
+  ushort sum = 0;
+  int x1 = x - 2;
+  int y1 = y - 2;
+  sum += 2 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 5 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 2 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  x1 = x - 2;
+  y1++;
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 9 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 12 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 9 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 5 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 12 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 15 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 12 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 5 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 9 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 12 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 9 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 2 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 5 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 4 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+  sum += 2 * rsGetElementAt_uchar(edgeImage, x1++, y1);
+
+  return (uchar)(sum / 159);
+}
+
+ushort __attribute__((kernel)) blurRGB(uint32_t x, uint32_t y) {
+  ushort sum = 0;
+  int x1 = x - 2;
+  int y1 = y - 2;
+  sum += 2 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 5 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 2 * getY(gCurrentRGBFrame, x1++, y1);
+  x1 = x - 2;
+  y1++;
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 9 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 12 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 9 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 5 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 12 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 15 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 12 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 5 * getY(gCurrentRGBFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 9 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 12 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 9 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 2 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 5 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 4 * getY(gCurrentRGBFrame, x1++, y1);
+  sum += 2 * getY(gCurrentRGBFrame, x1++, y1);
+
+  return sum / 159;
+}
+
+ushort __attribute__((kernel)) blur(uint32_t x, uint32_t y) {
+  ushort sum = 0;
+  int x1 = x - 2;
+  int y1 = y - 2;
+  sum += 2 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 5 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 2 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  x1 = x - 2;
+  y1++;
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 9 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 12 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 9 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 5 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 12 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 15 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 12 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 5 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 9 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 12 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 9 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+
+  x1 = x - 2;
+  y1++;
+  sum += 2 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 5 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 4 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+  sum += 2 * rsGetElementAtYuv_uchar_Y(gCurrentFrame, x1++, y1);
+
+  return sum / 159;
+}
+
+uchar __attribute__((kernel)) edge(uint32_t x, uint32_t y) {
+  int sum_h = 0;
+  int x1 = x - 1;
+  int y1 = y - 1;
+  sum_h += -rsGetElementAt_uchar(blurImage, x1, y1);
+  x += 2;
+  sum_h += rsGetElementAt_uchar(blurImage, x1, y1);
+  x1 = x - 1;
+  y1++;
+
+  sum_h += -2 * rsGetElementAt_uchar(blurImage, x1, y1);
+  x1 += 2;
+  sum_h += 2 * rsGetElementAt_uchar(blurImage, x1, y1);
+  x1 = x - 1;
+  y1++;
+
+  sum_h += -rsGetElementAt_uchar(blurImage, x1, y1);
+  x1 += 2;
+  sum_h += rsGetElementAt_uchar(blurImage, x1, y1);
+  int sum_v = 0;
+
+  x1 = x - 1;
+  y1 = y - 1;
+  sum_v += -rsGetElementAt_uchar(blurImage, x1++, y1);
+  sum_v += -2 * rsGetElementAt_uchar(blurImage, x1++, y1);
+  sum_v += -rsGetElementAt_uchar(blurImage, x1++, y1);
+
+  x1 = x - 1;
+  y1 += 2;
+  sum_v += rsGetElementAt_uchar(blurImage, x1++, y1);
+  sum_v += 2 * rsGetElementAt_uchar(blurImage, x1++, y1);
+  sum_v += rsGetElementAt_uchar(blurImage, x1++, y1);
+  int v = (int)native_hypot((float)sum_v, (float)sum_h);
+  v = v & (~3);
+  int dir = (int)(4 * native_atan2pi((float)sum_v, (float)sum_h) + 8.5f);
+
+  return (uchar)(v + (dir & 0x3));
+}
+
+uchar __attribute__((kernel)) thin(uint32_t x, uint32_t y) {
+  int value = rsGetElementAt_uchar(edgeImage, x, y);
+
+  int dir = value & 0x3;
+  int dx[4] = {1, 1, 0, 1};
+  int dy[4] = {0, 1, 1, -1};
+  if (value < rsGetElementAt_uchar(edgeImage, x + dx[dir], y + dy[dir])) {
+    return 0;
+  }
+  if (value < rsGetElementAt_uchar(edgeImage, x - dx[dir], y - dy[dir])) {
+    return 0;
+  }
+  return value >> 2;
+}
+
+static const short dark_line = 4;
+static const short bright_line = 16;
+#define q_add(v)                 \
+  {                              \
+    if (q_size < 64) {           \
+      queue[q_start & 0x3F] = v; \
+      q_start++;                 \
+      q_size++;                  \
+    }                            \
+  }
+#define q_peak() (queue[q_peek_pos & 0x3F])
+#define q_pop() (q_size--, q_tmp = queue[q_end], q_end++, q_tmp)
+
+uchar __attribute__((kernel)) hysteresis(uchar in, uint32_t x, uint32_t y) {
+  short3 queue[64];
+  int q_start = 0;
+  int q_end = 0;
+  int q_size = 0;
+  short3 q_tmp;
+  int q_peek_pos = 0;
+  int dx[] = {-1, 0, 1, -1, 1, -1, 0, 1};
+  int dy[] = {-1, -1, -1, 0, 0, 1, 1, 1};
+  if (in < dark_line) {
+    return 0;
+  }
+  if (in >= bright_line) {
+    return in;
+  }
+  short3 p = {0, 0, 1};
+  q_add(p);
+
+  while (q_peek_pos != q_start) {
+    if (q_size > 60) {
+      return 0;
+    }
+    p = q_peak();
+    q_peek_pos++;
+    short cdx = p[0];
+    short cdy = p[1];
+
+    int p_dist = p.z;
+    short v;
+    while (queue[q_end & 0x3F].z < p_dist - 1) {
+      q_pop();
+    }
+    for (int i = 0; i < 8; i++) {
+      int tx = cdx + dx[i];
+      int ty = cdy + dy[i];
+      bool skip = false;
+      for (int k = q_end; k < q_start; k++) {
+        short3 tq = queue[k & 0x3F];
+        if (tq.x == tx && tq.y == ty) {
+          skip = true;
+          break;
+        }
+      }
+      if (!(tx + x >= -5 && tx <= 5 && ty >= -5 && ty <= 5)) {
+        skip = true;
+      }
+      if (skip) continue;
+
+      v = rsGetElementAt_uchar(edgeImage, tx + x, ty + y);
+      if (v >= bright_line) {
+        return in;
+      }
+      if (v >= dark_line) {
+        short3 tmp = {tx, ty, p_dist + 1};
+        q_add(tmp);
+      }
+      q_peek_pos = (q_peek_pos + 1) & 0x3F;
+    }
+  }
+  return 0;
+}
+
+rs_allocation hough_output;  // width * height
+
+static inline float2 cos_sin(int n) {
+  float2 cs[] = {{1.0f,0.0f},
+                                  {0.9999619f,0.008726535f},{0.9998477f,0.017452406f},{0.99965733f,0.026176948f},
+                                  {0.99939084f,0.034899496f},{0.99904823f,0.043619387f},{0.9986295f,0.052335955f},
+                                  {0.9981348f,0.06104854f},{0.9975641f,0.06975647f},{0.9969173f,0.0784591f},
+                                  {0.9961947f,0.087155744f},{0.9953962f,0.09584575f},{0.9945219f,0.104528464f},
+                                  {0.9935719f,0.11320321f},{0.99254614f,0.12186934f},{0.9914449f,0.13052619f},
+                                  {0.99026805f,0.1391731f},{0.9890159f,0.14780942f},{0.98768836f,0.15643446f},
+                                  {0.9862856f,0.1650476f},{0.9848077f,0.17364818f},{0.9832549f,0.18223552f},
+                                  {0.98162717f,0.190809f},{0.9799247f,0.19936794f},{0.9781476f,0.20791169f},
+                                  {0.976296f,0.21643962f},{0.97437006f,0.22495106f},{0.9723699f,0.23344536f},
+                                  {0.9702957f,0.2419219f},{0.96814764f,0.25038f},{0.9659258f,0.25881904f},
+                                  {0.96363044f,0.26723838f},{0.9612617f,0.27563736f},{0.95881975f,0.28401536f},
+                                  {0.9563047f,0.2923717f},{0.95371693f,0.3007058f},{0.95105654f,0.309017f},
+                                  {0.94832367f,0.31730467f},{0.94551855f,0.32556817f},{0.9426415f,0.33380687f},
+                                  {0.9396926f,0.34202015f},{0.9366722f,0.3502074f},{0.9335804f,0.35836795f},
+                                  {0.9304176f,0.3665012f},{0.92718387f,0.37460658f},{0.9238795f,0.38268343f},
+                                  {0.92050487f,0.39073113f},{0.9170601f,0.39874908f},{0.9135454f,0.40673664f},
+                                  {0.9099613f,0.41469324f},{0.9063078f,0.42261827f},{0.90258527f,0.4305111f},
+                                  {0.89879405f,0.43837115f},{0.89493436f,0.4461978f},{0.8910065f,0.4539905f},
+                                  {0.8870108f,0.4617486f},{0.88294756f,0.46947157f},{0.87881714f,0.47715876f},
+                                  {0.8746197f,0.4848096f},{0.8703557f,0.49242356f},{0.8660254f,0.5f},
+                                  {0.8616292f,0.5075384f},{0.8571673f,0.5150381f},{0.85264015f,0.52249855f},
+                                  {0.8480481f,0.52991927f},{0.8433914f,0.53729963f},{0.83867055f,0.54463905f},
+                                  {0.83388585f,0.551937f},{0.82903755f,0.5591929f},{0.8241262f,0.56640625f},
+                                  {0.81915206f,0.57357645f},{0.8141155f,0.58070296f},{0.809017f,0.58778524f},
+                                  {0.80385685f,0.59482276f},{0.7986355f,0.60181504f},{0.7933533f,0.6087614f},
+                                  {0.7880108f,0.6156615f},{0.78260815f,0.62251467f},{0.777146f,0.6293204f},
+                                  {0.77162457f,0.63607824f},{0.76604444f,0.64278764f},{0.76040596f,0.64944804f},
+                                  {0.7547096f,0.656059f},{0.7489557f,0.66262007f},{0.7431448f,0.6691306f},
+                                  {0.7372773f,0.6755902f},{0.7313537f,0.6819984f},{0.7253744f,0.68835455f},
+                                  {0.7193398f,0.6946584f},{0.71325046f,0.70090926f},{0.70710677f,0.70710677f},
+                                  {0.70090926f,0.71325046f},{0.6946584f,0.7193398f},{0.68835455f,0.7253744f},
+                                  {0.6819984f,0.7313537f},{0.6755902f,0.7372773f},{0.6691306f,0.7431448f},
+                                  {0.66262007f,0.7489557f},{0.656059f,0.7547096f},{0.64944804f,0.76040596f},
+                                  {0.64278764f,0.76604444f},{0.63607824f,0.77162457f},{0.6293204f,0.777146f},
+                                  {0.62251467f,0.78260815f},{0.6156615f,0.7880108f},{0.6087614f,0.7933533f},
+                                  {0.60181504f,0.7986355f},{0.59482276f,0.80385685f},{0.58778524f,0.809017f},
+                                  {0.58070296f,0.8141155f},{0.57357645f,0.81915206f},{0.56640625f,0.8241262f},
+                                  {0.5591929f,0.82903755f},{0.551937f,0.83388585f},{0.54463905f,0.83867055f},
+                                  {0.53729963f,0.8433914f},{0.52991927f,0.8480481f},{0.52249855f,0.85264015f},
+                                  {0.5150381f,0.8571673f},{0.5075384f,0.8616292f},{0.5f,0.8660254f},
+                                  {0.49242356f,0.8703557f},{0.4848096f,0.8746197f},{0.47715876f,0.87881714f},
+                                  {0.46947157f,0.88294756f},{0.4617486f,0.8870108f},{0.4539905f,0.8910065f},
+                                  {0.4461978f,0.89493436f},{0.43837115f,0.89879405f},{0.4305111f,0.90258527f},
+                                  {0.42261827f,0.9063078f},{0.41469324f,0.9099613f},{0.40673664f,0.9135454f},
+                                  {0.39874908f,0.9170601f},{0.39073113f,0.92050487f},{0.38268343f,0.9238795f},
+                                  {0.37460658f,0.92718387f},{0.3665012f,0.9304176f},{0.35836795f,0.9335804f},
+                                  {0.3502074f,0.9366722f},{0.34202015f,0.9396926f},{0.33380687f,0.9426415f},
+                                  {0.32556817f,0.94551855f},{0.31730467f,0.94832367f},{0.309017f,0.95105654f},
+                                  {0.3007058f,0.95371693f},{0.2923717f,0.9563047f},{0.28401536f,0.95881975f},
+                                  {0.27563736f,0.9612617f},{0.26723838f,0.96363044f},{0.25881904f,0.9659258f},
+                                  {0.25038f,0.96814764f},{0.2419219f,0.9702957f},{0.23344536f,0.9723699f},
+                                  {0.22495106f,0.97437006f},{0.21643962f,0.976296f},{0.20791169f,0.9781476f},
+                                  {0.19936794f,0.9799247f},{0.190809f,0.98162717f},{0.18223552f,0.9832549f},
+                                  {0.17364818f,0.9848077f},{0.1650476f,0.9862856f},{0.15643446f,0.98768836f},
+                                  {0.14780942f,0.9890159f},{0.1391731f,0.99026805f},{0.13052619f,0.9914449f},
+                                  {0.12186934f,0.99254614f},{0.11320321f,0.9935719f},{0.104528464f,0.9945219f},
+                                  {0.09584575f,0.9953962f},{0.087155744f,0.9961947f},{0.0784591f,0.9969173f},
+                                  {0.06975647f,0.9975641f},{0.06104854f,0.9981348f},{0.052335955f,0.9986295f},
+                                  {0.043619387f,0.99904823f},{0.034899496f,0.99939084f},{0.026176948f,0.99965733f},
+                                  {0.017452406f,0.9998477f},{0.008726535f,0.9999619f},{6.123234E-17f,1.0f},
+                                  {-0.008726535f,0.9999619f},{-0.017452406f,0.9998477f},{-0.026176948f,0.99965733f},
+                                  {-0.034899496f,0.99939084f},{-0.043619387f,0.99904823f},{-0.052335955f,0.9986295f},
+                                  {-0.06104854f,0.9981348f},{-0.06975647f,0.9975641f},{-0.0784591f,0.9969173f},
+                                  {-0.087155744f,0.9961947f},{-0.09584575f,0.9953962f},{-0.104528464f,0.9945219f},
+                                  {-0.11320321f,0.9935719f},{-0.12186934f,0.99254614f},{-0.13052619f,0.9914449f},
+                                  {-0.1391731f,0.99026805f},{-0.14780942f,0.9890159f},{-0.15643446f,0.98768836f},
+                                  {-0.1650476f,0.9862856f},{-0.17364818f,0.9848077f},{-0.18223552f,0.9832549f},
+                                  {-0.190809f,0.98162717f},{-0.19936794f,0.9799247f},{-0.20791169f,0.9781476f},
+                                  {-0.21643962f,0.976296f},{-0.22495106f,0.97437006f},{-0.23344536f,0.9723699f},
+                                  {-0.2419219f,0.9702957f},{-0.25038f,0.96814764f},{-0.25881904f,0.9659258f},
+                                  {-0.26723838f,0.96363044f},{-0.27563736f,0.9612617f},{-0.28401536f,0.95881975f},
+                                  {-0.2923717f,0.9563047f},{-0.3007058f,0.95371693f},{-0.309017f,0.95105654f},
+                                  {-0.31730467f,0.94832367f},{-0.32556817f,0.94551855f},{-0.33380687f,0.9426415f},
+                                  {-0.34202015f,0.9396926f},{-0.3502074f,0.9366722f},{-0.35836795f,0.9335804f},
+                                  {-0.3665012f,0.9304176f},{-0.37460658f,0.92718387f},{-0.38268343f,0.9238795f},
+                                  {-0.39073113f,0.92050487f},{-0.39874908f,0.9170601f},{-0.40673664f,0.9135454f},
+                                  {-0.41469324f,0.9099613f},{-0.42261827f,0.9063078f},{-0.4305111f,0.90258527f},
+                                  {-0.43837115f,0.89879405f},{-0.4461978f,0.89493436f},{-0.4539905f,0.8910065f},
+                                  {-0.4617486f,0.8870108f},{-0.46947157f,0.88294756f},{-0.47715876f,0.87881714f},
+                                  {-0.4848096f,0.8746197f},{-0.49242356f,0.8703557f},{-0.5f,0.8660254f},
+                                  {-0.5075384f,0.8616292f},{-0.5150381f,0.8571673f},{-0.52249855f,0.85264015f},
+                                  {-0.52991927f,0.8480481f},{-0.53729963f,0.8433914f},{-0.54463905f,0.83867055f},
+                                  {-0.551937f,0.83388585f},{-0.5591929f,0.82903755f},{-0.56640625f,0.8241262f},
+                                  {-0.57357645f,0.81915206f},{-0.58070296f,0.8141155f},{-0.58778524f,0.809017f},
+                                  {-0.59482276f,0.80385685f},{-0.60181504f,0.7986355f},{-0.6087614f,0.7933533f},
+                                  {-0.6156615f,0.7880108f},{-0.62251467f,0.78260815f},{-0.6293204f,0.777146f},
+                                  {-0.63607824f,0.77162457f},{-0.64278764f,0.76604444f},{-0.64944804f,0.76040596f},
+                                  {-0.656059f,0.7547096f},{-0.66262007f,0.7489557f},{-0.6691306f,0.7431448f},
+                                  {-0.6755902f,0.7372773f},{-0.6819984f,0.7313537f},{-0.68835455f,0.7253744f},
+                                  {-0.6946584f,0.7193398f},{-0.70090926f,0.71325046f},{-0.70710677f,0.70710677f},
+                                  {-0.71325046f,0.70090926f},{-0.7193398f,0.6946584f},{-0.7253744f,0.68835455f},
+                                  {-0.7313537f,0.6819984f},{-0.7372773f,0.6755902f},{-0.7431448f,0.6691306f},
+                                  {-0.7489557f,0.66262007f},{-0.7547096f,0.656059f},{-0.76040596f,0.64944804f},
+                                  {-0.76604444f,0.64278764f},{-0.77162457f,0.63607824f},{-0.777146f,0.6293204f},
+                                  {-0.78260815f,0.62251467f},{-0.7880108f,0.6156615f},{-0.7933533f,0.6087614f},
+                                  {-0.7986355f,0.60181504f},{-0.80385685f,0.59482276f},{-0.809017f,0.58778524f},
+                                  {-0.8141155f,0.58070296f},{-0.81915206f,0.57357645f},{-0.8241262f,0.56640625f},
+                                  {-0.82903755f,0.5591929f},{-0.83388585f,0.551937f},{-0.83867055f,0.54463905f},
+                                  {-0.8433914f,0.53729963f},{-0.8480481f,0.52991927f},{-0.85264015f,0.52249855f},
+                                  {-0.8571673f,0.5150381f},{-0.8616292f,0.5075384f},{-0.8660254f,0.5f},
+                                  {-0.8703557f,0.49242356f},{-0.8746197f,0.4848096f},{-0.87881714f,0.47715876f},
+                                  {-0.88294756f,0.46947157f},{-0.8870108f,0.4617486f},{-0.8910065f,0.4539905f},
+                                  {-0.89493436f,0.4461978f},{-0.89879405f,0.43837115f},{-0.90258527f,0.4305111f},
+                                  {-0.9063078f,0.42261827f},{-0.9099613f,0.41469324f},{-0.9135454f,0.40673664f},
+                                  {-0.9170601f,0.39874908f},{-0.92050487f,0.39073113f},{-0.9238795f,0.38268343f},
+                                  {-0.92718387f,0.37460658f},{-0.9304176f,0.3665012f},{-0.9335804f,0.35836795f},
+                                  {-0.9366722f,0.3502074f},{-0.9396926f,0.34202015f},{-0.9426415f,0.33380687f},
+                                  {-0.94551855f,0.32556817f},{-0.94832367f,0.31730467f},{-0.95105654f,0.309017f},
+                                  {-0.95371693f,0.3007058f},{-0.9563047f,0.2923717f},{-0.95881975f,0.28401536f},
+                                  {-0.9612617f,0.27563736f},{-0.96363044f,0.26723838f},{-0.9659258f,0.25881904f},
+                                  {-0.96814764f,0.25038f},{-0.9702957f,0.2419219f},{-0.9723699f,0.23344536f},
+                                  {-0.97437006f,0.22495106f},{-0.976296f,0.21643962f},{-0.9781476f,0.20791169f},
+                                  {-0.9799247f,0.19936794f},{-0.98162717f,0.190809f},{-0.9832549f,0.18223552f},
+                                  {-0.9848077f,0.17364818f},{-0.9862856f,0.1650476f},{-0.98768836f,0.15643446f},
+                                  {-0.9890159f,0.14780942f},{-0.99026805f,0.1391731f},{-0.9914449f,0.13052619f},
+                                  {-0.99254614f,0.12186934f},{-0.9935719f,0.11320321f},{-0.9945219f,0.104528464f},
+                                  {-0.9953962f,0.09584575f},{-0.9961947f,0.087155744f},{-0.9969173f,0.0784591f},
+                                  {-0.9975641f,0.06975647f},{-0.9981348f,0.06104854f},{-0.9986295f,0.052335955f},
+                                  {-0.99904823f,0.043619387f},{-0.99939084f,0.034899496f},{-0.99965733f,0.026176948f},
+                                  {-0.9998477f,0.017452406f},{-0.9999619f,0.008726535f},};
+  return cs[n];
+}
+
+uchar4 __attribute__((kernel)) hough_map(uint32_t x, uint32_t y) {
+  int w = rsAllocationGetDimX(hough_output);
+  int h = rsAllocationGetDimY(hough_output);
+  int ix = clamp((int)x, 0, w - 1);
+  int iy = clamp((int)y, 0, h - 1);
+  int v = rsGetElementAt_uchar(hough_output, ix, iy);
+  uchar4 out = {clamp(v * 5, 0, 255), clamp(v, 0, 255), clamp(v, 0, 255), 255};
+  return out;
+}
+
+/* input is the range of angles to cover */
+void __attribute__((kernel)) hough(int2 in) {
+  int max_pos = rsAllocationGetDimX(hough_output);
+  int pos_shift = max_pos >> 1;
+  int start = in.x;
+  int end = in.y;
+  int width = rsAllocationGetDimX(edgeImage);
+  int height = rsAllocationGetDimY(edgeImage);
+  float half_height = height >> 1;
+  float half_width = width >> 1;
+  for (int iy = 0; iy < height; iy++) {
+    for (int ix = 0; ix < width; ix++) {
+      if (rsGetElementAt_uchar(edgeImage, ix, iy) > 20) {
+        float2 pos = {ix - half_width, iy - half_height};
+        for (int ang = start; ang < end; ang++) {
+          int r = pos_shift + (int)dot(pos, cos_sin(ang));
+          if (r >= 0 && r < max_pos) {
+            ushort tmp = rsGetElementAt_uchar(hough_output, r, ang);
+            rsSetElementAt_uchar(hough_output, tmp + 1, r, ang);
+          }
+        }
+      }
+    }
+  }
+}
+
+uchar __attribute__((kernel)) hough_thin(uchar in, int x, int y) {
+  short tmp = rsGetElementAt_ushort(hough_output, x - 1, y - 1);
+  if (tmp > in) return 0;
+  tmp = rsGetElementAt_uchar(hough_output, x + 1, y + 1);
+  if (tmp > in) return 0;
+
+  tmp = rsGetElementAt_uchar(hough_output, x, y - 1);
+  if (tmp > in) return 0;
+  tmp = rsGetElementAt_uchar(hough_output, x + 1, y - 1);
+  if (tmp > in) return 0;
+
+  tmp = rsGetElementAt_uchar(hough_output, x - 1, y);
+  if (tmp > in) return 0;
+
+  tmp = rsGetElementAt_uchar(hough_output, x + 1, y);
+  if (tmp > in) return 0;
+
+  tmp = rsGetElementAt_uchar(hough_output, x - 1, y + 1);
+  if (tmp > in) return 0;
+  tmp = rsGetElementAt_uchar(hough_output, x, y + 1);
+  if (tmp > in) return 0;
+  return in;
+}