Add SilkFX demo app

Silk prototyping area for HDR & other exploration

Test: this
Change-Id: I3b8b7d23454f26fa0842a59a4c82f503c0a58897
diff --git a/tests/SilkFX/Android.bp b/tests/SilkFX/Android.bp
new file mode 100644
index 0000000..ca0a091
--- /dev/null
+++ b/tests/SilkFX/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2010 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.
+//
+
+android_test {
+    name: "SilkFX",
+    srcs: ["**/*.java", "**/*.kt"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml
new file mode 100644
index 0000000..ca9550a
--- /dev/null
+++ b/tests/SilkFX/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.test.silkfx">
+
+    <uses-sdk android:minSdkVersion="30"/>
+
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+
+    <application android:label="SilkFX"
+         android:theme="@android:style/Theme.Material">
+
+        <activity android:name=".Main"
+             android:label="SilkFX Demos"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.CommonDemoActivity" />
+
+        <activity android:name=".hdr.GlowActivity"
+            android:label="Glow Examples"/>
+
+    </application>
+</manifest>
diff --git a/tests/SilkFX/res/drawable-nodpi/dark_notification.png b/tests/SilkFX/res/drawable-nodpi/dark_notification.png
new file mode 100644
index 0000000..6de6c2a
--- /dev/null
+++ b/tests/SilkFX/res/drawable-nodpi/dark_notification.png
Binary files differ
diff --git a/tests/SilkFX/res/drawable-nodpi/light_notification.png b/tests/SilkFX/res/drawable-nodpi/light_notification.png
new file mode 100644
index 0000000..81a67cd
--- /dev/null
+++ b/tests/SilkFX/res/drawable-nodpi/light_notification.png
Binary files differ
diff --git a/tests/SilkFX/res/layout/bling_notifications.xml b/tests/SilkFX/res/layout/bling_notifications.xml
new file mode 100644
index 0000000..6d266b7
--- /dev/null
+++ b/tests/SilkFX/res/layout/bling_notifications.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <com.android.test.silkfx.hdr.BlingyNotification
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:src="@drawable/dark_notification" />
+
+    <com.android.test.silkfx.hdr.BlingyNotification
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:src="@drawable/light_notification" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/color_mode_controls.xml b/tests/SilkFX/res/layout/color_mode_controls.xml
new file mode 100644
index 0000000..c0c0bab
--- /dev/null
+++ b/tests/SilkFX/res/layout/color_mode_controls.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<com.android.test.silkfx.common.ColorModeControls
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/current_mode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/mode_default"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Default (sRGB)" />
+
+        <Button
+            android:id="@+id/mode_wide"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="Wide Gamut (P3)" />
+
+        <Button
+            android:id="@+id/mode_hdr"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="HDR" />
+
+        <Button
+            android:id="@+id/mode_hdr10"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="HDR10" />
+
+    </LinearLayout>
+
+</com.android.test.silkfx.common.ColorModeControls>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/common_base.xml b/tests/SilkFX/res/layout/common_base.xml
new file mode 100644
index 0000000..944c684
--- /dev/null
+++ b/tests/SilkFX/res/layout/common_base.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/color_mode_controls" />
+
+    <FrameLayout android:id="@+id/demo_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <com.android.test.silkfx.common.HDRIndicator
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_margin="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/res/layout/hdr_glows.xml b/tests/SilkFX/res/layout/hdr_glows.xml
new file mode 100644
index 0000000..b6050645
--- /dev/null
+++ b/tests/SilkFX/res/layout/hdr_glows.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/color_mode_controls" />
+
+    <com.android.test.silkfx.hdr.GlowingCard
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_margin="8dp" />
+
+    <com.android.test.silkfx.hdr.GlowingCard
+        android:id="@+id/card2"
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_margin="8dp"/>
+
+    <com.android.test.silkfx.hdr.RadialGlow
+        android:layout_width="match_parent"
+        android:layout_height="200dp"
+        android:layout_margin="8dp" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <com.android.test.silkfx.common.HDRIndicator
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_margin="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
new file mode 100644
index 0000000..76e62a6
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseExpandableListAdapter
+import android.widget.ExpandableListView
+import android.widget.TextView
+import com.android.test.silkfx.app.CommonDemoActivity
+import com.android.test.silkfx.app.EXTRA_LAYOUT
+import com.android.test.silkfx.app.EXTRA_TITLE
+import com.android.test.silkfx.hdr.GlowActivity
+import kotlin.reflect.KClass
+
+class Demo(val name: String, val makeIntent: (Context) -> Intent) {
+    constructor(name: String, activity: KClass<out Activity>) : this(name, { context ->
+        Intent(context, activity.java)
+    })
+    constructor(name: String, layout: Int) : this(name, { context ->
+        Intent(context, CommonDemoActivity::class.java).apply {
+            putExtra(EXTRA_LAYOUT, layout)
+            putExtra(EXTRA_TITLE, name)
+        }
+    })
+}
+data class DemoGroup(val groupName: String, val demos: List<Demo>)
+
+private val AllDemos = listOf(
+        DemoGroup("HDR", listOf(
+                Demo("Glow", GlowActivity::class),
+                Demo("Blingy Notifications", R.layout.bling_notifications)
+        ))
+)
+
+class Main : Activity() {
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val list = ExpandableListView(this)
+
+        setContentView(list)
+
+        val inflater = LayoutInflater.from(this)
+        list.setAdapter(object : BaseExpandableListAdapter() {
+            override fun getGroup(groupPosition: Int): DemoGroup {
+                return AllDemos[groupPosition]
+            }
+
+            override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = true
+
+            override fun hasStableIds(): Boolean = true
+
+            override fun getGroupView(
+                groupPosition: Int,
+                isExpanded: Boolean,
+                convertView: View?,
+                parent: ViewGroup?
+            ): View {
+                val view = (convertView ?: inflater.inflate(
+                        android.R.layout.simple_expandable_list_item_1, parent, false)) as TextView
+                view.text = AllDemos[groupPosition].groupName
+                return view
+            }
+
+            override fun getChildrenCount(groupPosition: Int): Int {
+                return AllDemos[groupPosition].demos.size
+            }
+
+            override fun getChild(groupPosition: Int, childPosition: Int): Demo {
+                return AllDemos[groupPosition].demos[childPosition]
+            }
+
+            override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
+
+            override fun getChildView(
+                groupPosition: Int,
+                childPosition: Int,
+                isLastChild: Boolean,
+                convertView: View?,
+                parent: ViewGroup?
+            ): View {
+                val view = (convertView ?: inflater.inflate(
+                        android.R.layout.simple_expandable_list_item_1, parent, false)) as TextView
+                view.text = AllDemos[groupPosition].demos[childPosition].name
+                return view
+            }
+
+            override fun getChildId(groupPosition: Int, childPosition: Int): Long {
+                return (groupPosition.toLong() shl 32) or childPosition.toLong()
+            }
+
+            override fun getGroupCount(): Int {
+                return AllDemos.size
+            }
+        })
+
+        list.setOnChildClickListener { _, _, groupPosition, childPosition, _ ->
+            val demo = AllDemos[groupPosition].demos[childPosition]
+            startActivity(demo.makeIntent(this))
+            return@setOnChildClickListener true
+        }
+
+        AllDemos.forEachIndexed { index, _ -> list.expandGroup(index) }
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt
new file mode 100644
index 0000000..89011b5
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/BaseDemoActivity.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.app
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+
+open class BaseDemoActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val inflater = LayoutInflater.from(this)
+        inflater.factory2 = object : LayoutInflater.Factory2 {
+            private val sClassPrefixList = arrayOf(
+                    "android.widget.",
+                    "android.webkit.",
+                    "android.app.",
+                    null
+            )
+            override fun onCreateView(
+                parent: View?,
+                name: String,
+                context: Context,
+                attrs: AttributeSet
+            ): View? {
+                return onCreateView(name, context, attrs)
+            }
+
+            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
+                for (prefix in sClassPrefixList) {
+                    try {
+                        val view = inflater.createView(name, prefix, attrs)
+                        if (view != null) {
+                            if (view is WindowObserver) {
+                                view.setWindow(window)
+                            }
+                            return view
+                        }
+                    } catch (e: ClassNotFoundException) { }
+                }
+                return null
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        actionBar?.setDisplayHomeAsUpEnabled(true)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        if (item.itemId == android.R.id.home) {
+            onBackPressed()
+            return true
+        }
+        return super.onOptionsItemSelected(item)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt
new file mode 100644
index 0000000..e0a0a20
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.app
+
+import com.android.test.silkfx.R
+import android.os.Bundle
+import android.view.LayoutInflater
+
+const val EXTRA_LAYOUT = "layout"
+const val EXTRA_TITLE = "title"
+
+class CommonDemoActivity : BaseDemoActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val extras = intent.extras ?: return finish()
+
+        val layout = extras.getInt(EXTRA_LAYOUT, -1)
+        if (layout == -1) {
+            finish()
+            return
+        }
+        val title = extras.getString(EXTRA_TITLE, "SilkFX")
+        window.setTitle(title)
+
+        setContentView(R.layout.common_base)
+        actionBar?.title = title
+        LayoutInflater.from(this).inflate(layout, findViewById(R.id.demo_container), true)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt b/tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt
new file mode 100644
index 0000000..3d989a5
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/app/WindowObserver.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.app
+
+import android.view.Window
+
+interface WindowObserver {
+    fun setWindow(window: Window)
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt
new file mode 100644
index 0000000..4b85953
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.common
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.util.AttributeSet
+import android.view.View
+
+open class BaseDrawingView : View {
+    val scRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
+    val bt2020 = ColorSpace.get(ColorSpace.Named.BT2020)
+    val lab = ColorSpace.get(ColorSpace.Named.CIE_LAB)
+
+    val density: Float
+    val dp: Int.() -> Float
+
+    fun color(red: Float, green: Float, blue: Float, alpha: Float = 1f): Long {
+        return Color.pack(red, green, blue, alpha, scRGB)
+    }
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        setWillNotDraw(false)
+        isClickable = true
+        density = resources.displayMetrics.density
+        dp = { this * density }
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
new file mode 100644
index 0000000..c3d689c
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.common
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.hardware.display.DisplayManager
+import android.util.AttributeSet
+import android.view.Window
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.test.silkfx.R
+import com.android.test.silkfx.app.WindowObserver
+import java.lang.Exception
+
+class ColorModeControls : LinearLayout, WindowObserver {
+    private val COLOR_MODE_HDR10 = 3
+
+    constructor(context: Context) : this(context, null)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        displayManager = context.getSystemService(DisplayManager::class.java)!!
+    }
+
+    private var window: Window? = null
+    private var currentMode: TextView? = null
+    private val displayManager: DisplayManager
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        val window = window ?: throw IllegalStateException("Failed to attach window")
+
+        currentMode = findViewById(R.id.current_mode)!!
+        setColorMode(window.colorMode)
+
+        findViewById<Button>(R.id.mode_default)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_DEFAULT)
+        }
+        findViewById<Button>(R.id.mode_wide)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT)
+        }
+        findViewById<Button>(R.id.mode_hdr)!!.setOnClickListener {
+            setColorMode(ActivityInfo.COLOR_MODE_HDR)
+        }
+        findViewById<Button>(R.id.mode_hdr10)!!.setOnClickListener {
+            setColorMode(COLOR_MODE_HDR10)
+        }
+    }
+
+    private fun setColorMode(newMode: Int) {
+        val window = window!!
+        // Need to do this before setting the colorMode, as setting the colorMode will
+        // trigger the attribute change listener
+        if (newMode == ActivityInfo.COLOR_MODE_HDR ||
+                newMode == COLOR_MODE_HDR10) {
+            setBrightness(1.0f)
+        } else {
+            setBrightness(.4f)
+        }
+        window.colorMode = newMode
+        currentMode?.run {
+            text = "Current Mode: " + when (newMode) {
+                ActivityInfo.COLOR_MODE_DEFAULT -> "Default/SRGB"
+                ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT -> "Wide Gamut"
+                ActivityInfo.COLOR_MODE_HDR -> "HDR (sdr white point 150)"
+                COLOR_MODE_HDR10 -> "HDR10 (sdr white point 150)"
+                else -> "Unknown"
+            }
+        }
+    }
+
+    override fun setWindow(window: Window) {
+        this.window = window
+    }
+
+    private fun setBrightness(level: Float) {
+        // To keep window state in sync
+        window?.attributes?.screenBrightness = level
+        invalidate()
+        // To force an 'immediate' snap to what we want
+        // Imperfect, but close enough, synchronization by waiting for frame commit to set the value
+        viewTreeObserver.registerFrameCommitCallback {
+            try {
+                displayManager.setTemporaryBrightness(level)
+            } catch (ex: Exception) {
+                // Ignore a permission denied rejection - it doesn't meaningfully change much
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt b/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt
new file mode 100644
index 0000000..f42161f
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/common/HDRIndicator.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.common
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.graphics.Paint
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.View
+
+class HDRIndicator(context: Context) : View(context) {
+    constructor(context: Context, attrs: AttributeSet?) : this(context)
+
+    val scRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        val paint = Paint()
+        paint.isAntiAlias = true
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+        paint.textSize = height.toFloat()
+
+        canvas.drawColor(Color.pack(1f, 1f, 1f, 1f, scRGB))
+
+        paint.setColor(Color.pack(1.1f, 1.1f, 1.1f, 1f, scRGB))
+        canvas.drawText("H", rect.left, rect.bottom, paint)
+        paint.setColor(Color.pack(1.2f, 1.2f, 1.2f, 1f, scRGB))
+        canvas.drawText("D", rect.left + height.toFloat(), rect.bottom, paint)
+        paint.setColor(Color.pack(1.3f, 1.3f, 1.3f, 1f, scRGB))
+        canvas.drawText("R", rect.left + height.toFloat() * 2, rect.bottom, paint)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt
new file mode 100644
index 0000000..e517c3c
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/BlingyNotification.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.LinearGradient
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.graphics.drawable.BitmapDrawable
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+
+class BlingyNotification : BaseDrawingView {
+
+    private val image: Bitmap?
+    private val bounds = Rect()
+    private val paint = Paint()
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        val typed = context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.src))
+        val drawable = typed.getDrawable(0)
+        image = if (drawable is BitmapDrawable) {
+            drawable.bitmap
+        } else {
+            null
+        }
+        typed.recycle()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val image = image ?: return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+
+        // Currently only used in this mode, so that's all we'll bother to support
+        if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
+            val width = MeasureSpec.getSize(widthMeasureSpec)
+
+            var height = image.height * width / image.width
+            if (heightMode == MeasureSpec.AT_MOST) {
+                height = minOf(MeasureSpec.getSize(heightMeasureSpec), height)
+            }
+            setMeasuredDimension(width, height)
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        }
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        bounds.set(0, 0, w, h)
+        paint.shader = LinearGradient(0f, 0f, w.toFloat(), 0f,
+                longArrayOf(
+                        color(1f, 1f, 1f, 0f),
+                        color(1f, 1f, 1f, .4f),
+                        color(2f, 2f, 2f, .8f),
+                        color(1f, 1f, 1f, .4f),
+                        color(1f, 1f, 1f, 0f)
+                        ),
+                floatArrayOf(.2f, .4f, .5f, .6f, .8f),
+                Shader.TileMode.CLAMP)
+        paint.blendMode = BlendMode.PLUS
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+
+        val image = image ?: return
+
+        canvas.drawBitmap(image, null, bounds, null)
+
+        canvas.save()
+        val frac = ((drawingTime % 2000) / 300f) - 1f
+        canvas.translate(width * frac, 0f)
+        canvas.rotate(-45f)
+        canvas.drawPaint(paint)
+        canvas.restore()
+        invalidate()
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt
new file mode 100644
index 0000000..64dbb22
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.hdr
+
+import android.os.Bundle
+import com.android.test.silkfx.R
+import com.android.test.silkfx.app.BaseDemoActivity
+
+class GlowActivity : BaseDemoActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.hdr_glows)
+        findViewById<GlowingCard>(R.id.card2)!!.setGlowIntensity(4f)
+    }
+}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt
new file mode 100644
index 0000000..b388bb6
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GlowingCard.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.LinearGradient
+import android.graphics.Paint
+import android.graphics.RectF
+import android.graphics.Shader
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+
+class GlowingCard : BaseDrawingView {
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+    val radius: Float
+    var COLOR_MAXIMIZER = 1f
+
+    init {
+        radius = 10.dp()
+    }
+
+    fun setGlowIntensity(multiplier: Float) {
+        COLOR_MAXIMIZER = multiplier
+        invalidate()
+    }
+
+    override fun setPressed(pressed: Boolean) {
+        super.setPressed(pressed)
+        invalidate()
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        val paint = Paint()
+        paint.isAntiAlias = true
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+        val glowColor = Color.pack(.5f * COLOR_MAXIMIZER, .4f * COLOR_MAXIMIZER,
+                .75f * COLOR_MAXIMIZER, 1f, scRGB)
+
+        if (isPressed) {
+            paint.setColor(Color.pack(2f, 2f, 2f, 1f, scRGB))
+            paint.strokeWidth = 4.dp()
+            paint.style = Paint.Style.FILL
+            paint.shader = LinearGradient(rect.left, rect.bottom, rect.right, rect.top,
+                glowColor,
+                Color.pack(0f, 0f, 0f, 0f, scRGB),
+                Shader.TileMode.CLAMP)
+            canvas.drawRoundRect(rect, radius, radius, paint)
+        }
+
+        rect.inset(3.dp(), 3.dp())
+
+        paint.setColor(Color.pack(.14f, .14f, .14f, .8f, scRGB))
+        paint.style = Paint.Style.FILL
+        paint.shader = null
+        canvas.drawRoundRect(rect, radius, radius, paint)
+
+        rect.inset(5.dp(), 5.dp())
+        paint.textSize = 14.dp()
+        paint.isFakeBoldText = true
+
+        paint.color = Color.WHITE
+        canvas.drawText("glow = scRGB{${Color.red(glowColor)}, ${Color.green(glowColor)}, " +
+                "${Color.blue(glowColor)}}", rect.left, rect.centerY(), paint)
+        canvas.drawText("(press to activate)", rect.left, rect.bottom, paint)
+    }
+}
\ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt
new file mode 100644
index 0000000..599585e
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RadialGradient
+import android.graphics.RectF
+import android.graphics.Shader
+import android.util.AttributeSet
+import com.android.test.silkfx.common.BaseDrawingView
+import kotlin.math.min
+
+class RadialGlow : BaseDrawingView {
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+    var glowToggle = false
+
+    val glowColor = color(4f, 3.3f, 2.8f)
+    val bgColor = color(.15f, .15f, .15f)
+    val fgColor = color(.51f, .52f, .50f, .4f)
+    var glow: RadialGradient
+
+    init {
+        glow = RadialGradient(0f, 0f, 100.dp(), glowColor, bgColor, Shader.TileMode.CLAMP)
+        isClickable = true
+        setOnClickListener {
+            glowToggle = !glowToggle
+            invalidate()
+        }
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        glow = RadialGradient(0f, 0f,
+            min(w, h).toFloat(), glowColor, bgColor, Shader.TileMode.CLAMP)
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        val radius = 10.dp()
+
+        val paint = Paint()
+        paint.isDither = true
+        paint.isAntiAlias = true
+        paint.textSize = 18.dp()
+        paint.textAlign = Paint.Align.CENTER
+
+        val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+
+        paint.setColor(bgColor)
+        canvas.drawRoundRect(rect, radius, radius, paint)
+
+        if (glowToggle) {
+            paint.shader = glow
+            canvas.save()
+            val frac = (drawingTime % 5000) / 5000f
+            canvas.translate(rect.width() * frac, rect.height() - (rect.height() * frac))
+            canvas.drawPaint(paint)
+            canvas.restore()
+            paint.shader = null
+            invalidate()
+        }
+
+        paint.setColor(fgColor)
+        val innerRect = RectF(rect)
+        innerRect.inset(rect.width() / 4, rect.height() / 4)
+        canvas.drawRoundRect(innerRect, radius, radius, paint)
+
+        paint.setColor(color(1f, 1f, 1f))
+        canvas.drawText("Tap to toggle animation", rect.centerX(), innerRect.top - 4.dp(), paint)
+        canvas.drawText("Outside text", rect.centerX(), rect.bottom - 4.dp(), paint)
+        canvas.drawText("Inside text", innerRect.centerX(), innerRect.bottom - 4.dp(), paint)
+    }
+}
\ No newline at end of file