Import Cobalt 21.lts.4.300899
diff --git a/src/buildtools/linux64/gn b/src/buildtools/linux64/gn
deleted file mode 100755
index 9485341..0000000
--- a/src/buildtools/linux64/gn
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 644ecae..b201080 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -125,6 +125,18 @@
    This behavior can now be overridden by implementing the cobalt extension
    function `CobaltExtensionGraphicsApi::ShouldClearFrameOnShutdown`.
 
+ - **Added support for adjusting colors of 360 videos.**
+
+   Platforms which support 360 videos may adjust the colors of the video frame
+   using `CobaltExtensionGraphicsApi::GetMapToMeshColorAdjustments`.
+
+ - **Added support for rendering the frame with a custom root transform.**
+
+   Platforms can force frame rendering to use a custom root transform by using
+   `CobaltExtensionGraphicsApi::GetRenderRootTransform`. This only impacts
+   rendering; the web app does not know about the custom transform so may not
+   layout elements appropriately.
+
 ## Version 20
 
  - **Support for QUIC and SPDY is now enabled.**
@@ -483,7 +495,7 @@
    The Cobalt splash screen is customizable. Documents may use a link element
    with attribute rel="splashscreen" to reference the splash screen which will
    be cached if local cache is implemented on the platform. Additionally
-   fallbacks may be specified via command line parmeter or gypi variable.
+   fallbacks may be specified via command line parameter or gypi variable.
    For more information, see [doc/splash_screen.md](doc/splash_screen.md).
 
  - **Introduce C\+\+11**
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 9bdf0dc..4021ee4 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-300530
\ No newline at end of file
+300899
\ No newline at end of file
diff --git a/src/cobalt/content/ssl/certs/9c2e7d30.0 b/src/cobalt/content/ssl/certs/9c2e7d30.0
deleted file mode 100644
index 36a998d..0000000
--- a/src/cobalt/content/ssl/certs/9c2e7d30.0
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
-MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
-MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
-BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
-Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
-5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
-3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
-vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
-8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
-DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
-MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
-zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
-3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
-FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
-Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
-ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
------END CERTIFICATE-----
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 0670fbb..1bbc6c8 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -63,7 +63,7 @@
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
   EXPECT_GE(extension_api->version, 1u);
-  EXPECT_LE(extension_api->version, 4u);
+  EXPECT_LE(extension_api->version, 5u);
 
   EXPECT_NE(extension_api->GetMaximumFrameIntervalInMilliseconds, nullptr);
   float maximum_frame_interval =
@@ -97,6 +97,11 @@
     }
   }
 
+  if (extension_api->version >= 5) {
+    EXPECT_NE(extension_api->GetMapToMeshColorAdjustments, nullptr);
+    EXPECT_NE(extension_api->GetRenderRootTransform, nullptr);
+  }
+
   const ExtensionApi* second_extension_api =
       static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
diff --git a/src/cobalt/extension/graphics.h b/src/cobalt/extension/graphics.h
index a1a3b4e..ed55a2e 100644
--- a/src/cobalt/extension/graphics.h
+++ b/src/cobalt/extension/graphics.h
@@ -25,6 +25,21 @@
 
 #define kCobaltExtensionGraphicsName "dev.cobalt.extension.Graphics"
 
+// This structure allows post-processing of output colors for 360 videos.
+// Given "rgba" is the color that the pixel shader calculates, this struct
+// allows additional processing in the form of:
+//   final color = rgba0_scale +
+//                 rgba1_scale * rgba +
+//                 rgba2_scale * rgba * rgba +
+//                 rgba3_scale * rgba * rgba * rgba;
+// The final_color is then clamped to the range of [0,1] for each element.
+typedef struct CobaltExtensionGraphicsMapToMeshColorAdjustment {
+  float rgba0_scale[4];  // multiplier for rgba^0
+  float rgba1_scale[4];  // multiplier for rgba^1
+  float rgba2_scale[4];  // multiplier for rgba^2
+  float rgba3_scale[4];  // multiplier for rgba^3
+} CobaltExtensionGraphicsMapToMeshColorAdjustment;
+
 typedef struct CobaltExtensionGraphicsApi {
   // Name should be the string kCobaltExtensionGraphicsName.
   // This helps to validate that the extension API is correct.
@@ -75,6 +90,23 @@
                                      float* clear_color_green,
                                      float* clear_color_blue,
                                      float* clear_color_alpha);
+
+  // The fields below this point were added in version 5 or later.
+
+  // Use the provided color adjustments for 360 videos if the function returns
+  // true. See declaration of CobaltExtensionGraphicsMapToMeshColorAdjustment
+  // for details.
+  bool (*GetMapToMeshColorAdjustments)(
+      CobaltExtensionGraphicsMapToMeshColorAdjustment* adjustment);
+
+  // This function can be used to insert a custom transform at the root of
+  // rendering. This allows custom scaling, rotating, etc. of the frame. This
+  // only impacts rendering of the frame -- the web app will not know about this
+  // transform, so it may not layout elements appropriately. This function
+  // should return true if a custom transform should be used.
+  bool (*GetRenderRootTransform)(float* m00, float* m01, float* m02, float* m10,
+                                 float* m11, float* m12, float* m20, float* m21,
+                                 float* m22);
 } CobaltExtensionGraphicsApi;
 
 #ifdef __cplusplus
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc
index b635518..6ab79ff 100644
--- a/src/cobalt/layout/layout.cc
+++ b/src/cobalt/layout/layout.cc
@@ -24,12 +24,14 @@
 #include "cobalt/dom/html_body_element.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/html_html_element.h"
+#include "cobalt/extension/graphics.h"
 #include "cobalt/layout/benchmark_stat_names.h"
 #include "cobalt/layout/box_generator.h"
 #include "cobalt/layout/initial_containing_block.h"
 #include "cobalt/layout/layout_boxes.h"
 #include "cobalt/layout/used_style.h"
 #include "cobalt/render_tree/animations/animate_node.h"
+#include "cobalt/render_tree/matrix_transform_node.h"
 
 namespace cobalt {
 namespace layout {
@@ -194,9 +196,26 @@
   // status for animated images.
   used_style_provider->UpdateAnimatedImages();
 
-  render_tree::CompositionNode* static_root_node =
+  render_tree::Node* static_root_node =
       new render_tree::CompositionNode(std::move(render_tree_root_builder));
 
+#if SB_API_VERSION >= 11
+  // Support insertion of a custom transform at the render tree root.
+  static const CobaltExtensionGraphicsApi* s_graphics_extension =
+      static_cast<const CobaltExtensionGraphicsApi*>(
+          SbSystemGetExtension(kCobaltExtensionGraphicsName));
+  float m00, m01, m02, m10, m11, m12, m20, m21, m22;
+  if (s_graphics_extension &&
+      strcmp(s_graphics_extension->name, kCobaltExtensionGraphicsName) == 0 &&
+      s_graphics_extension->version >= 5 &&
+      s_graphics_extension->GetRenderRootTransform(&m00, &m01, &m02, &m10, &m11,
+                                                   &m12, &m20, &m21, &m22)) {
+    static_root_node = new render_tree::MatrixTransformNode(
+        static_root_node, math::Matrix3F::FromValues(m00, m01, m02, m10, m11,
+                                                     m12, m20, m21, m22));
+  }
+#endif  // SB_API_VERSION >= 11
+
   // Make it easy to animate the entire tree by placing an AnimateNode at the
   // root to merge any sub-AnimateNodes.
   render_tree::animations::AnimateNode* animate_node =
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl
index f42f765..ddeb45d 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-precision mediump float;
+precision highp float;
 uniform vec4 u_include;   // include scissor (x_min, y_min, x_max, y_max)
 varying vec2 v_offset;
 varying vec4 v_color;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
index 04eaa9c..fdffec9 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
@@ -4,5 +4,6 @@
 

 void main() {

   vec4 untransformed_color = vec4(texture2D(texture_rgba, v_tex_coord_rgba).rgba);

-  gl_FragColor = untransformed_color;

-}
\ No newline at end of file
+  vec4 color = untransformed_color;

+  gl_FragColor = color;

+}

diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
index 9a77757..1a36b31 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
@@ -30,5 +30,6 @@
   }

 

   vec4 untransformed_color = vec4(y_value, uv_value.r, uv_value.g, 1.0);

-  gl_FragColor = untransformed_color * to_rgb_color_matrix;

+  vec4 color = untransformed_color * to_rgb_color_matrix;

+  gl_FragColor = color;

 }

diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl
index 75b5036..15c79fa 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl
@@ -10,5 +10,6 @@
       texture2D(texture_y, v_tex_coord_y).a,

       texture2D(texture_uv, v_tex_coord_uv).ba, 1.0);

 

-  gl_FragColor = untransformed_color * to_rgb_color_matrix;

+  vec4 color = untransformed_color * to_rgb_color_matrix;

+  gl_FragColor = color;

 }

diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl
index 91137c6..da6e748 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl
@@ -11,5 +11,6 @@
     texture2D(texture_y, v_tex_coord_y).a,

     texture2D(texture_u, v_tex_coord_u).a,

     texture2D(texture_v, v_tex_coord_v).a, 1.0);

-  gl_FragColor = untransformed_color * to_rgb_color_matrix;

+  vec4 color = untransformed_color * to_rgb_color_matrix;

+  gl_FragColor = color;

 }

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl
index 2cb71f7..aadf5d0 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-precision mediump float;
+precision highp float;
 uniform vec4 u_include;   // include scissor (x_min, y_min, x_max, y_max)
 varying vec2 v_offset;
 varying vec4 v_color;
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 874c225..ac3cb80 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "base/strings/stringprintf.h"
+#include "cobalt/extension/graphics.h"
 #include "cobalt/math/size.h"
 #include "cobalt/renderer/backend/egl/utils.h"
 #include "cobalt/renderer/egl_and_gles.h"
@@ -346,12 +347,53 @@
   if (color_matrix) {
     blit_fragment_shader_source +=
         ");"
-        "  gl_FragColor = untransformed_color * to_rgb_color_matrix;"
-        "}";
+        "  vec4 color = untransformed_color * to_rgb_color_matrix;";
   } else {
     blit_fragment_shader_source +=
         ");"
-        "  gl_FragColor = untransformed_color;"
+        "  vec4 color = untransformed_color;";
+  }
+#if SB_API_VERSION >= 11
+  const CobaltExtensionGraphicsApi* graphics_extension =
+      static_cast<const CobaltExtensionGraphicsApi*>(
+          SbSystemGetExtension(kCobaltExtensionGraphicsName));
+  CobaltExtensionGraphicsMapToMeshColorAdjustment color_adjustment;
+  if (graphics_extension != nullptr &&
+      strcmp(graphics_extension->name, kCobaltExtensionGraphicsName) == 0 &&
+      graphics_extension->version >= 5 &&
+      graphics_extension->GetMapToMeshColorAdjustments(&color_adjustment)) {
+    // Setup vectors for the color adjustments. Ensure they use dot for decimal
+    // point regardless of locale.
+    std::stringstream buffer;
+    buffer.imbue(std::locale::classic());
+    buffer << "  vec4 scale0 = vec4(" << color_adjustment.rgba0_scale[0] << ","
+           << color_adjustment.rgba0_scale[1] << ","
+           << color_adjustment.rgba0_scale[2] << ","
+           << color_adjustment.rgba0_scale[3] << ");";
+    buffer << "  vec4 scale1 = vec4(" << color_adjustment.rgba1_scale[0] << ","
+           << color_adjustment.rgba1_scale[1] << ","
+           << color_adjustment.rgba1_scale[2] << ","
+           << color_adjustment.rgba1_scale[3] << ");";
+    buffer << "  vec4 scale2 = vec4(" << color_adjustment.rgba2_scale[0] << ","
+           << color_adjustment.rgba2_scale[1] << ","
+           << color_adjustment.rgba2_scale[2] << ","
+           << color_adjustment.rgba2_scale[3] << ");";
+    buffer << "  vec4 scale3 = vec4(" << color_adjustment.rgba3_scale[0] << ","
+           << color_adjustment.rgba3_scale[1] << ","
+           << color_adjustment.rgba3_scale[2] << ","
+           << color_adjustment.rgba3_scale[3] << ");";
+    blit_fragment_shader_source +=
+        "  vec4 color2 = color * color;"
+        "  vec4 color3 = color2 * color;" +
+        buffer.str() +
+        "  color = scale0 + scale1*color + scale2*color2 + scale3*color3;"
+        "  gl_FragColor = clamp(color, vec4(0.0), vec4(1.0));"
+        "}";
+  } else
+#endif  // SB_API_VERSION >= 11
+  {
+    blit_fragment_shader_source +=
+        "  gl_FragColor = color;"
         "}";
   }
 
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
index 537a25f..2558ad5 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -45,8 +45,6 @@
 #include "cobalt/renderer/rasterizer/skia/font.h"
 #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h"
 #include "cobalt/renderer/rasterizer/skia/image.h"
-#include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h"
-#include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h"
 #include "cobalt/renderer/rasterizer/skia/skottie_animation.h"
 #include "cobalt/renderer/rasterizer/skia/software_image.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/skia_cobalt.gypi b/src/cobalt/renderer/rasterizer/skia/skia/skia_cobalt.gypi
index 6f73d3e..72ac12e 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/skia_cobalt.gypi
+++ b/src/cobalt/renderer/rasterizer/skia/skia/skia_cobalt.gypi
@@ -19,10 +19,6 @@
 
   'sources': [
     'config/SkUserConfig.h',
-    'src/effects/SkNV122RGBShader.cc',
-    'src/effects/SkNV122RGBShader.h',
-    'src/effects/SkYUV2RGBShader.cc',
-    'src/effects/SkYUV2RGBShader.h',
     'src/google_logging.cc',
     'src/gpu/gl/GrGLMakeNativeInterface_cobalt.cc',
     'src/ports/SkFontConfigParser_cobalt.cc',
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.cc
deleted file mode 100644
index 8f9f343..0000000
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// 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.
-
-#include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "cobalt/base/polymorphic_downcast.h"
-#include "third_party/skia/include/core/SkColorPriv.h"
-#include "third_party/skia/include/core/SkShader.h"
-#include "third_party/skia/include/core/SkString.h"
-#include "third_party/skia/src/core/SkReadBuffer.h"
-#include "third_party/skia/src/core/SkWriteBuffer.h"
-
-#if SK_SUPPORT_GPU
-#include "third_party/skia/src/gpu/GrFragmentProcessor.h"
-#endif
-
-SkNV122RGBShader::SkNV122RGBShader(SkYUVColorSpace color_space,
-                                   const sk_sp<SkImage>& y_image,
-                                   const SkMatrix& y_matrix,
-                                   const sk_sp<SkImage>& uv_image,
-                                   const SkMatrix& uv_matrix)
-    : color_space_(color_space),
-      y_image_(y_image),
-      y_matrix_(y_matrix),
-      uv_image_(uv_image),
-      uv_matrix_(uv_matrix) {
-  DCHECK(y_image_);
-  DCHECK(uv_image_);
-  InitializeShaders();
-}
-
-void SkNV122RGBShader::InitializeShaders() {
-  y_shader_.reset(base::polymorphic_downcast<SkImageShader*>(
-      SkImageShader::Make(y_image_, SkTileMode::kClamp, SkTileMode::kClamp,
-                          &y_matrix_, false)
-          .get()));
-  DCHECK(y_shader_);
-
-  uv_shader_.reset(base::polymorphic_downcast<SkImageShader*>(
-      SkImageShader::Make(uv_image_, SkTileMode::kClamp, SkTileMode::kClamp,
-                          &uv_matrix_, false)
-          .get()));
-  DCHECK(uv_shader_);
-}
-
-SkNV122RGBShader::~SkNV122RGBShader() {
-}
-
-void SkNV122RGBShader::flatten(SkWriteBuffer& buffer) const {
-  buffer.writeInt(color_space_);
-  buffer.writeImage(y_image_.get());
-  buffer.writeMatrix(y_matrix_);
-  buffer.writeImage(uv_image_.get());
-  buffer.writeMatrix(uv_matrix_);
-}
-
-uint32_t SkNV122RGBShader::NV122RGBShaderContext::getFlags() const { return 0; }
-
-SkNV122RGBShader::NV122RGBShaderContext::NV122RGBShaderContext(
-    SkYUVColorSpace color_space, const SkNV122RGBShader& yuv2rgb_shader,
-    SkShaderBase::Context* y_shader_context,
-    SkShaderBase::Context* uv_shader_context, const ContextRec& rec)
-    : INHERITED(yuv2rgb_shader, rec),
-      color_space_(color_space),
-      y_shader_context_(y_shader_context),
-      uv_shader_context_(uv_shader_context) {}
-
-SkNV122RGBShader::NV122RGBShaderContext::~NV122RGBShaderContext() {
-  y_shader_context_->~Context();
-  uv_shader_context_->~Context();
-}
-
-void SkNV122RGBShader::NV122RGBShaderContext::shadeSpan(int x, int y,
-                                                        SkPMColor result[],
-                                                        int count) {
-  static const int kPixelCountPerSpan = 64;
-  SkPMColor y_values[kPixelCountPerSpan];
-  SkPMColor uv_values[kPixelCountPerSpan];
-
-  DCHECK_EQ(kRec709_SkYUVColorSpace, color_space_)
-      << "Currently we only support the BT.709 YUV colorspace.";
-
-  do {
-    int count_in_chunk =
-        count > kPixelCountPerSpan ? kPixelCountPerSpan : count;
-
-    y_shader_context_->shadeSpan(x, y, y_values, count_in_chunk);
-    uv_shader_context_->shadeSpan(x, y, uv_values, count_in_chunk);
-
-    for (int i = 0; i < count_in_chunk; ++i) {
-      int32_t y_value = SkColorGetA(y_values[i]) - 16;
-      int32_t u_value = SkColorGetB(uv_values[i]) - 128;
-      int32_t v_value = SkColorGetA(uv_values[i]) - 128;
-
-      const float kA = 1.164f;
-      const float kB = -0.213f;
-      const float kC = 2.112f;
-      const float kD = 1.793f;
-      const float kE = -0.533f;
-      int32_t r_unclamped = static_cast<int32_t>(kA * y_value + kD * v_value);
-      int32_t g_unclamped =
-          static_cast<int32_t>(kA * y_value + kB * u_value + kE * v_value);
-      int32_t b_unclamped = static_cast<int32_t>(kA * y_value + kC * u_value);
-
-      int32_t r_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, r_unclamped));
-      int32_t g_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, g_unclamped));
-      int32_t b_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, b_unclamped));
-
-      result[i] = SkPackARGB32NoCheck(255, r_clamped, g_clamped, b_clamped);
-    }
-    result += count_in_chunk;
-    x += count_in_chunk;
-    count -= count_in_chunk;
-  } while (count > 0);
-}
-
-#if SK_SUPPORT_GPU
-
-std::unique_ptr<GrFragmentProcessor> SkNV122RGBShader::asFragmentProcessor(
-    const GrFPArgs&) const {
-#if 0
-  // TODO SKIA_M61_EFFECTS.  Investigate if we still need this.
-  // Code snippet taken from SkBitmapProcShader::asFragmentProcessor().
-  SkMatrix matrix;
-  matrix.setIDiv(y_image_->width(), y_image_->height());
-
-  SkMatrix lmInverse;
-  if (!y_shader_->getLocalMatrix().invert(&lmInverse)) {
-    return false;
-  }
-  if (localMatrix) {
-    SkMatrix inv;
-    if (!localMatrix->invert(&inv)) {
-      return false;
-    }
-    lmInverse.postConcat(inv);
-  }
-  matrix.preConcat(lmInverse);
-
-  GrTextureParams::FilterMode filter_mode =
-      paint.getFilterLevel() == SkPaint::kNone_FilterLevel
-          ? GrTextureParams::kNone_FilterMode
-          : GrTextureParams::kBilerp_FilterMode;
-  GrTextureParams texture_params;
-  texture_params.setFilterMode(filter_mode);
-
-  *fp = GrYUVtoRGBEffect::Create(y_image_->getTexture(),
-                                 uv_image_->getTexture(),
-                                 NULL, matrix, texture_params,
-                                 kRec709_SkYUVColorSpace, true);
-  return true;
-#else
-  return nullptr;
-#endif
-}
-
-#endif  // SK_SUPPORT_GPU
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h b/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h
deleted file mode 100644
index c822333..0000000
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// 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.
-
-#ifndef COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKNV122RGBSHADER_H_
-#define COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKNV122RGBSHADER_H_
-
-#include "third_party/skia/src/shaders/SkShaderBase.h"
-
-#include "third_party/skia/src/shaders/SkImageShader.h"
-
-class SkNV122RGBShader : public SkShaderBase {
- public:
-  SkNV122RGBShader(SkYUVColorSpace color_space, const sk_sp<SkImage>& y_image,
-                   const SkMatrix& y_matrix, const sk_sp<SkImage>& uv_image,
-                   const SkMatrix& uv_matrix);
-  virtual ~SkNV122RGBShader();
-
-  class NV122RGBShaderContext : public SkShaderBase::Context {
-   public:
-    // Takes ownership of shaderContext and calls its destructor.
-    NV122RGBShaderContext(SkYUVColorSpace color_space,
-                          const SkNV122RGBShader& yuv2rgb_shader,
-                          SkShaderBase::Context* y_shader_context,
-                          SkShaderBase::Context* uv_shader_context,
-                          const ContextRec& rec);
-    virtual ~NV122RGBShaderContext();
-
-    uint32_t getFlags() const override;
-
-    void shadeSpan(int x, int y, SkPMColor[], int count) override;
-
-   private:
-    SkYUVColorSpace color_space_;
-
-    SkShaderBase::Context* y_shader_context_;
-    SkShaderBase::Context* uv_shader_context_;
-
-    typedef SkShaderBase::Context INHERITED;
-  };
-
-#if SK_SUPPORT_GPU
-  std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-      const GrFPArgs&) const override;
-#endif
-
-  SK_FLATTENABLE_HOOKS(SkNV122RGBShader)
-
- protected:
-  void flatten(SkWriteBuffer&) const override;
-
- private:
-  void InitializeShaders();
-
-  SkYUVColorSpace color_space_;
-
-  sk_sp<SkImage> y_image_;
-  SkMatrix y_matrix_;
-  sk_sp<SkImageShader> y_shader_;
-
-  sk_sp<SkImage> uv_image_;
-  SkMatrix uv_matrix_;
-  sk_sp<SkImageShader> uv_shader_;
-
-  typedef SkShaderBase INHERITED;
-};
-
-#endif  // COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKNV122RGBSHADER_H_
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.cc
deleted file mode 100644
index 71efe90..0000000
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// 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.
-
-#include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "cobalt/base/polymorphic_downcast.h"
-#include "third_party/skia/include/core/SkColorPriv.h"
-#include "third_party/skia/include/core/SkShader.h"
-#include "third_party/skia/include/core/SkString.h"
-#include "third_party/skia/src/core/SkReadBuffer.h"
-#include "third_party/skia/src/core/SkWriteBuffer.h"
-#include "third_party/skia/src/gpu/GrFragmentProcessor.h"
-#include "third_party/skia/src/gpu/effects/GrYUVtoRGBEffect.h"
-#include "third_party/skia/src/shaders/SkImageShader.h"
-
-SkYUV2RGBShader::SkYUV2RGBShader(SkYUVColorSpace color_space,
-                                 const sk_sp<SkImage>& y_image,
-                                 const SkMatrix& y_matrix,
-                                 const sk_sp<SkImage>& u_image,
-                                 const SkMatrix& u_matrix,
-                                 const sk_sp<SkImage>& v_image,
-                                 const SkMatrix& v_matrix)
-    : color_space_(color_space),
-      y_image_(y_image),
-      y_matrix_(y_matrix),
-      u_image_(u_image),
-      u_matrix_(u_matrix),
-      v_image_(v_image),
-      v_matrix_(v_matrix) {
-  DCHECK(y_image_);
-  DCHECK(u_image_);
-  DCHECK(v_image_);
-  InitializeShaders();
-}
-
-void SkYUV2RGBShader::InitializeShaders() {
-  y_shader_.reset(base::polymorphic_downcast<SkImageShader*>(
-      SkImageShader::Make(y_image_, SkTileMode::kClamp, SkTileMode::kClamp,
-                          &y_matrix_, false)
-          .get()));
-  DCHECK(y_shader_);
-
-  u_shader_.reset(base::polymorphic_downcast<SkImageShader*>(
-      SkImageShader::Make(u_image_, SkTileMode::kClamp, SkTileMode::kClamp,
-                          &u_matrix_, false)
-          .get()));
-  DCHECK(u_shader_);
-
-  v_shader_.reset(base::polymorphic_downcast<SkImageShader*>(
-      SkImageShader::Make(v_image_, SkTileMode::kClamp, SkTileMode::kClamp,
-                          &v_matrix_, false)
-          .get()));
-  DCHECK(v_shader_);
-}
-
-void SkYUV2RGBShader::flatten(SkWriteBuffer& buffer) const {
-  buffer.writeInt(color_space_);
-  buffer.writeImage(y_image_.get());
-  buffer.writeMatrix(y_matrix_);
-  buffer.writeImage(u_image_.get());
-  buffer.writeMatrix(u_matrix_);
-  buffer.writeImage(v_image_.get());
-  buffer.writeMatrix(v_matrix_);
-}
-
-uint32_t SkYUV2RGBShader::YUV2RGBShaderContext::getFlags() const {
-  return 0;
-}
-
-SkShaderBase::Context* SkYUV2RGBShader::onMakeContext(
-    const ContextRec& rec, SkArenaAlloc* storage) const {
-  return nullptr;
-}
-SkYUV2RGBShader::YUV2RGBShaderContext::YUV2RGBShaderContext(
-    SkYUVColorSpace color_space, const SkYUV2RGBShader& yuv2rgb_shader,
-    SkShaderBase::Context* y_shader_context,
-    SkShaderBase::Context* u_shader_context,
-    SkShaderBase::Context* v_shader_context, const ContextRec& rec)
-    : INHERITED(yuv2rgb_shader, rec),
-      color_space_(color_space),
-      y_shader_context_(y_shader_context),
-      u_shader_context_(u_shader_context),
-      v_shader_context_(v_shader_context) {}
-
-SkYUV2RGBShader::YUV2RGBShaderContext::~YUV2RGBShaderContext() {
-  y_shader_context_->~Context();
-  u_shader_context_->~Context();
-  v_shader_context_->~Context();
-}
-void SkYUV2RGBShader::YUV2RGBShaderContext::shadeSpan(
-    int x, int y, SkPMColor result[], int count) {
-  static const int kPixelCountPerSpan = 64;
-  SkPMColor y_values[kPixelCountPerSpan];
-  SkPMColor u_values[kPixelCountPerSpan];
-  SkPMColor v_values[kPixelCountPerSpan];
-
-  DCHECK_EQ(kRec709_SkYUVColorSpace, color_space_) <<
-      "Currently we only support the BT.709 YUV colorspace.";
-
-  do {
-    int count_in_chunk =
-        count > kPixelCountPerSpan ? kPixelCountPerSpan : count;
-
-    y_shader_context_->shadeSpan(x, y, y_values, count_in_chunk);
-    u_shader_context_->shadeSpan(x, y, u_values, count_in_chunk);
-    v_shader_context_->shadeSpan(x, y, v_values, count_in_chunk);
-
-    for (int i = 0; i < count_in_chunk; ++i) {
-      int32_t y_value = SkColorGetA(y_values[i]) - 16;
-      int32_t u_value = SkColorGetA(u_values[i]) - 128;
-      int32_t v_value = SkColorGetA(v_values[i]) - 128;
-
-      const float kA = 1.164f;
-      const float kB = -0.213f;
-      const float kC = 2.112f;
-      const float kD = 1.793f;
-      const float kE = -0.533f;
-      int32_t r_unclamped = static_cast<int32_t>(kA * y_value + kD * v_value);
-      int32_t g_unclamped =
-          static_cast<int32_t>(kA * y_value + kB * u_value + kE * v_value);
-      int32_t b_unclamped = static_cast<int32_t>(kA * y_value + kC * u_value);
-
-      int32_t r_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, r_unclamped));
-      int32_t g_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, g_unclamped));
-      int32_t b_clamped =
-          std::min<int32_t>(255, std::max<int32_t>(0, b_unclamped));
-
-      result[i] = SkPackARGB32NoCheck(255, r_clamped, g_clamped, b_clamped);
-    }
-    result += count_in_chunk;
-    x += count_in_chunk;
-    count -= count_in_chunk;
-  } while (count > 0);
-}
-
-#if SK_SUPPORT_GPU
-
-std::unique_ptr<GrFragmentProcessor> SkYUV2RGBShader::asFragmentProcessor(
-    const GrFPArgs&) const {
-  return nullptr;
-}
-
-#endif  // SK_SUPPORT_GPU
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h b/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h
deleted file mode 100644
index f05534b..0000000
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// 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.
-
-#ifndef COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKYUV2RGBSHADER_H_
-#define COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKYUV2RGBSHADER_H_
-
-#include "third_party/skia/src/shaders/SkShaderBase.h"
-
-#include "third_party/skia/src/shaders/SkImageShader.h"
-
-class SkYUV2RGBShader : public SkShaderBase {
- public:
-  SkYUV2RGBShader(SkYUVColorSpace color_space, const sk_sp<SkImage>& y_image,
-                  const SkMatrix& y_matrix, const sk_sp<SkImage>& u_image,
-                  const SkMatrix& u_matrix, const sk_sp<SkImage>& v_image,
-                  const SkMatrix& v_matrix);
-  virtual ~SkYUV2RGBShader() = default;
-
-  class YUV2RGBShaderContext : public SkShaderBase::Context {
-   public:
-    // Takes ownership of shaderContext and calls its destructor.
-    YUV2RGBShaderContext(SkYUVColorSpace color_space,
-                         const SkYUV2RGBShader& yuv2rgb_shader,
-                         SkShaderBase::Context* y_shader_context,
-                         SkShaderBase::Context* u_shader_context,
-                         SkShaderBase::Context* v_shader_context,
-                         const ContextRec& rec);
-    virtual ~YUV2RGBShaderContext();
-
-    uint32_t getFlags() const override;
-
-    void shadeSpan(int x, int y, SkPMColor[], int count) override;
-
-   private:
-    SkYUVColorSpace color_space_;
-
-    SkShaderBase::Context* y_shader_context_;
-    SkShaderBase::Context* u_shader_context_;
-    SkShaderBase::Context* v_shader_context_;
-
-    typedef SkShaderBase::Context INHERITED;
-  };
-
-#if SK_SUPPORT_GPU
-  std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
-      const GrFPArgs&) const override;
-#endif
-
-  SK_FLATTENABLE_HOOKS(SkYUV2RGBShader)
-
- protected:
-  void flatten(SkWriteBuffer&) const override;
-  SkShaderBase::Context* onMakeContext(const ContextRec& rec,
-                                       SkArenaAlloc* storage) const override;
-
- private:
-  void InitializeShaders();
-
-  SkYUVColorSpace color_space_;
-
-  sk_sp<SkImage> y_image_;
-  SkMatrix y_matrix_;
-  sk_sp<SkImageShader> y_shader_;
-
-  sk_sp<SkImage> u_image_;
-  SkMatrix u_matrix_;
-  sk_sp<SkImageShader> u_shader_;
-
-  sk_sp<SkImage> v_image_;
-  SkMatrix v_matrix_;
-  sk_sp<SkImageShader> v_shader_;
-
-  typedef SkShaderBase INHERITED;
-};
-
-#endif  // COBALT_RENDERER_RASTERIZER_SKIA_SKIA_SRC_EFFECTS_SKYUV2RGBSHADER_H_
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index ee9b712..4cba4f3 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -145,8 +145,13 @@
   updater_observer_.reset();
   update_client_ = nullptr;
 
-  updater_configurator_->GetPrefService()->CommitPendingWrite(
-      base::BindOnce(&QuitLoop, base::Bind(base::DoNothing::Repeatedly())));
+  if (updater_configurator_ != nullptr) {
+    auto pref_service = updater_configurator_->GetPrefService();
+    if (pref_service != nullptr) {
+      pref_service->CommitPendingWrite(
+          base::BindOnce(&QuitLoop, base::Bind(base::DoNothing::Repeatedly())));
+    }
+  }
 
   updater_configurator_ = nullptr;
 }
diff --git a/src/starboard/android/apk/app/src/app/AndroidManifest.xml b/src/starboard/android/apk/app/src/app/AndroidManifest.xml
index 7337495..bceef34 100644
--- a/src/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/src/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -67,4 +67,9 @@
 
   </application>
 
+  <queries>
+    <!-- This is needed to access the speech recognition service in SDK 30 -->
+    <package android:name="com.google.android.katniss" />
+  </queries>
+
 </manifest>
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index bf21be7..fc4907a 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -537,7 +537,16 @@
       return false;
     }
 
-    int[] supportedHdrTypes = defaultDisplay.getHdrCapabilities().getSupportedHdrTypes();
+    Display.HdrCapabilities hdrCapabilities = defaultDisplay.getHdrCapabilities();
+    if (hdrCapabilities == null) {
+      return false;
+    }
+
+    int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes();
+    if (supportedHdrTypes == null) {
+      return false;
+    }
+
     for (int supportedType : supportedHdrTypes) {
       if (supportedType == hdrType) {
         return true;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 547c67c..e3d0c3c 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -523,7 +523,7 @@
       MediaCrypto crypto) {
     MediaCodec mediaCodec = null;
     try {
-      String decoderName = MediaCodecUtil.findAudioDecoder(mime, 0);
+      String decoderName = MediaCodecUtil.findAudioDecoder(mime, 0, false);
       if (decoderName.equals("")) {
         Log.e(TAG, String.format("Failed to find decoder: %s, isSecure: %s", mime, isSecure));
         return null;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index be473bb..5318110 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -397,10 +397,19 @@
       int frameHeight,
       int bitrate,
       int fps,
-      boolean mustSupportHdr) {
+      boolean mustSupportHdr,
+      boolean mustSupportTunnelMode) {
     FindVideoDecoderResult findVideoDecoderResult =
         findVideoDecoder(
-            mimeType, secure, frameWidth, frameHeight, bitrate, fps, mustSupportHdr, false, false);
+            mimeType,
+            secure,
+            frameWidth,
+            frameHeight,
+            bitrate,
+            fps,
+            mustSupportHdr,
+            false,
+            mustSupportTunnelMode);
     return !findVideoDecoderResult.name.equals("")
         && (!mustSupportHdr || isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult));
   }
@@ -411,8 +420,9 @@
    */
   @SuppressWarnings("unused")
   @UsedByNative
-  public static boolean hasAudioDecoderFor(String mimeType, int bitrate) {
-    return !findAudioDecoder(mimeType, bitrate).equals("");
+  public static boolean hasAudioDecoderFor(
+      String mimeType, int bitrate, boolean mustSupportTunnelMode) {
+    return !findAudioDecoder(mimeType, bitrate, mustSupportTunnelMode).equals("");
   }
 
   /**
@@ -438,15 +448,6 @@
     return isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult);
   }
 
-  /** Determine whether the system support tunneled playback */
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public static boolean hasTunneledCapableDecoder(String mimeType, boolean isSecure) {
-    FindVideoDecoderResult findVideoDecoderResult =
-        findVideoDecoder(mimeType, isSecure, 0, 0, 0, 0, false, false, true);
-    return !findVideoDecoderResult.name.equals("");
-  }
-
   /** Determine whether findVideoDecoderResult is capable of playing HDR */
   public static boolean isHdrCapableVideoDecoder(
       String mimeType, FindVideoDecoderResult findVideoDecoderResult) {
@@ -723,7 +724,8 @@
    * The same as hasAudioDecoderFor, only return the name of the audio decoder if it is found, and
    * "" otherwise.
    */
-  public static String findAudioDecoder(String mimeType, int bitrate) {
+  public static String findAudioDecoder(
+      String mimeType, int bitrate, boolean requireTunneledPlayback) {
     // Note: MediaCodecList is sorted by the framework such that the best decoders come first.
     for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
       if (info.isEncoder()) {
@@ -734,13 +736,21 @@
           continue;
         }
         String name = info.getName();
-        AudioCapabilities audioCapabilities =
-            info.getCapabilitiesForType(supportedType).getAudioCapabilities();
+        CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType);
+        AudioCapabilities audioCapabilities = codecCapabilities.getAudioCapabilities();
         Range<Integer> bitrateRange =
             Range.create(0, audioCapabilities.getBitrateRange().getUpper());
         if (!bitrateRange.contains(bitrate)) {
           continue;
         }
+        if (requireTunneledPlayback
+            && !codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) {
+          continue;
+        }
+        // TODO: Determine if we can safely check if an audio codec requires the tunneled playback
+        //  feature. i.e., reject when |requireTunneledPlayback| == false
+        //  and codecCapabilities.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback) ==
+        //  true.
         return name;
       }
     }
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id
deleted file mode 100644
index 57a90bb..0000000
--- a/src/starboard/android/apk/build.id
+++ /dev/null
@@ -1 +0,0 @@
-300523
\ No newline at end of file
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
index e47aaaf..fe8b4ec 100644
--- a/src/starboard/android/shared/media_is_audio_supported.cc
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -16,6 +16,7 @@
 
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_common.h"
+#include "starboard/audio_sink.h"
 #include "starboard/configuration.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
@@ -47,14 +48,36 @@
   if (!enable_audio_routing_parameter_value.empty() &&
       enable_audio_routing_parameter_value != "true" &&
       enable_audio_routing_parameter_value != "false") {
-    SB_LOG(WARNING) << "Invalid value for enableaudiorouting: "
-                    << enable_audio_routing_parameter_value << ".";
+    SB_LOG(INFO)
+        << "Invalid value for audio mime parameter \"enableaudiorouting\": "
+        << enable_audio_routing_parameter_value << ".";
     return false;
   }
+  // Allows for enabling tunneled playback. Disabled by default.
+  // (https://source.android.com/devices/tv/multimedia-tunneling)
+  auto enable_tunnel_mode_parameter_value =
+      mime_type.GetParamStringValue("tunnelmode", "");
+  if (!enable_tunnel_mode_parameter_value.empty() &&
+      enable_tunnel_mode_parameter_value != "true" &&
+      enable_tunnel_mode_parameter_value != "false") {
+    SB_LOG(INFO) << "Invalid value for audio mime parameter \"tunnelmode\": "
+                 << enable_tunnel_mode_parameter_value << ".";
+    return false;
+  } else if (enable_tunnel_mode_parameter_value == "true" &&
+             !SbAudioSinkIsAudioSampleTypeSupported(
+                 kSbMediaAudioSampleTypeInt16Deprecated)) {
+    SB_LOG(WARNING)
+        << "Tunnel mode is rejected because int16 sample is required "
+           "but not supported.";
+    return false;
+  }
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+  const bool must_support_tunnel_mode =
+      enable_tunnel_mode_parameter_value == "true";
   return env->CallStaticBooleanMethodOrAbort(
              "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor",
-             "(Ljava/lang/String;I)Z", j_mime.Get(),
-             static_cast<jint>(bitrate)) == JNI_TRUE;
+             "(Ljava/lang/String;IZ)Z", j_mime.Get(),
+             static_cast<jint>(bitrate), must_support_tunnel_mode) == JNI_TRUE;
 }
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc
index a0c42d0..332e4b2 100644
--- a/src/starboard/android/shared/media_is_video_supported.cc
+++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -20,11 +20,13 @@
 #include "starboard/configuration.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/media/media_util.h"
+#include "starboard/shared/starboard/media/mime_type.h"
 
 using starboard::android::shared::JniEnvExt;
 using starboard::android::shared::ScopedLocalJavaRef;
 using starboard::android::shared::SupportedVideoCodecToMimeType;
 using starboard::shared::starboard::media::IsSDRVideo;
+using starboard::shared::starboard::media::MimeType;
 
 namespace {
 
@@ -93,13 +95,38 @@
   if (!mime) {
     return false;
   }
+  MimeType mime_type(content_type);
+  // Allows for enabling tunneled playback. Disabled by default.
+  // (https://source.android.com/devices/tv/multimedia-tunneling)
+  auto enable_tunnel_mode_parameter_value =
+      mime_type.GetParamStringValue("tunnelmode", "");
+  if (!enable_tunnel_mode_parameter_value.empty() &&
+      enable_tunnel_mode_parameter_value != "true" &&
+      enable_tunnel_mode_parameter_value != "false") {
+    SB_LOG(INFO) << "Invalid value for video mime parameter \"tunnelmode\": "
+                 << enable_tunnel_mode_parameter_value << ".";
+    return false;
+  } else if (enable_tunnel_mode_parameter_value == "true" &&
+             decode_to_texture_required) {
+    SB_LOG(WARNING) << "Tunnel mode is rejected because output mode decode to "
+                       "texture is required but not supported.";
+    return false;
+  }
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
-  bool must_support_hdr = (transfer_id != kSbMediaTransferIdBt709 &&
-                           transfer_id != kSbMediaTransferIdUnspecified);
+  const bool must_support_hdr = (transfer_id != kSbMediaTransferIdBt709 &&
+                                 transfer_id != kSbMediaTransferIdUnspecified);
+  const bool must_support_tunnel_mode =
+      enable_tunnel_mode_parameter_value == "true";
+  // We assume that if a device supports a format for clear playback, it will
+  // also support it for encrypted playback. However, some devices require
+  // tunneled playback to be encrypted, so we must align the tunnel mode
+  // requirement with the secure playback requirement.
+  const bool require_secure_playback = must_support_tunnel_mode;
   return env->CallStaticBooleanMethodOrAbort(
              "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-             "(Ljava/lang/String;ZIIIIZ)Z", j_mime.Get(), false, frame_width,
-             frame_height, static_cast<jint>(bitrate), fps,
-             must_support_hdr) == JNI_TRUE;
+             "(Ljava/lang/String;ZIIIIZZ)Z", j_mime.Get(),
+             require_secure_playback, frame_width, frame_height,
+             static_cast<jint>(bitrate), fps, must_support_hdr,
+             must_support_tunnel_mode) == JNI_TRUE;
 }
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index c771391..5e1b078 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -27,6 +27,7 @@
 #include "starboard/android/shared/video_decoder.h"
 #include "starboard/atomic.h"
 #include "starboard/common/log.h"
+#include "starboard/common/media.h"
 #include "starboard/common/ref_counted.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/media.h"
@@ -46,9 +47,8 @@
 namespace android {
 namespace shared {
 
-// Tunnel mode is disabled by default.  Set the following variable to true to
-// enable tunnel mode.
-constexpr bool kTunnelModeEnabled = false;
+using starboard::shared::starboard::media::MimeType;
+
 // On some platforms tunnel mode is only supported in the secure pipeline.  Set
 // the following variable to true to force creating a secure pipeline in tunnel
 // mode, even for clear content.
@@ -162,8 +162,43 @@
     SB_DCHECK(error_message);
 
     int tunnel_mode_audio_session_id = -1;
+    bool enable_tunnel_mode = false;
+    if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone &&
+        creation_parameters.video_codec() != kSbMediaVideoCodecNone) {
+      MimeType audio_mime_type(creation_parameters.audio_mime());
+      MimeType video_mime_type(creation_parameters.video_mime());
+      auto enable_tunnel_mode_audio_parameter_value =
+          audio_mime_type.GetParamStringValue("tunnelmode", "");
+      auto enable_tunnel_mode_video_parameter_value =
+          video_mime_type.GetParamStringValue("tunnelmode", "");
+      if (enable_tunnel_mode_audio_parameter_value == "true" &&
+          enable_tunnel_mode_video_parameter_value == "true") {
+        enable_tunnel_mode = true;
+      } else {
+        if (enable_tunnel_mode_audio_parameter_value.empty()) {
+          enable_tunnel_mode_audio_parameter_value = "not provided";
+        }
+        if (enable_tunnel_mode_video_parameter_value.empty()) {
+          enable_tunnel_mode_video_parameter_value = "not provided";
+        }
+        SB_LOG(INFO) << "Tunnel mode is disabled. Audio mime parameter "
+                        "\"tunnelmode\" value: "
+                     << enable_tunnel_mode_audio_parameter_value
+                     << ", video mime parameter \"tunnelmode\" value: "
+                     << enable_tunnel_mode_video_parameter_value << ".";
+      }
+    } else {
+      SB_LOG(INFO) << "Tunnel mode requires both an audio and video stream. "
+                   << "Audio codec: "
+                   << GetMediaAudioCodecName(creation_parameters.audio_codec())
+                   << ", Video codec: "
+                   << GetMediaVideoCodecName(creation_parameters.video_codec())
+                   << ". Tunnel mode is disabled.";
+    }
+
     bool force_secure_pipeline_under_tunnel_mode = false;
-    if (IsTunnelModeSupported(creation_parameters,
+    if (enable_tunnel_mode &&
+        IsTunnelModeSupported(creation_parameters,
                               &force_secure_pipeline_under_tunnel_mode)) {
       tunnel_mode_audio_session_id =
           GenerateAudioSessionId(creation_parameters);
@@ -205,16 +240,15 @@
           GetExtendedDrmSystem(creation_parameters.drm_system()),
           decoder_creator));
       bool enable_audio_routing = true;
-      starboard::shared::starboard::media::MimeType mime_type(
-          creation_parameters.audio_mime());
+      MimeType audio_mime_type(creation_parameters.audio_mime());
       auto enable_audio_routing_parameter_value =
-          mime_type.GetParamStringValue("enableaudiorouting", "");
+          audio_mime_type.GetParamStringValue("enableaudiorouting", "");
       if (enable_audio_routing_parameter_value.empty() ||
           enable_audio_routing_parameter_value == "true") {
         SB_LOG(INFO) << "AudioRouting is enabled.";
       } else {
         enable_audio_routing = false;
-        SB_LOG(INFO) << "Mime attribute enableaudiorouting is set to: "
+        SB_LOG(INFO) << "Mime attribute \"enableaudiorouting\" is set to: "
                      << enable_audio_routing_parameter_value
                      << ". AudioRouting is disabled.";
       }
@@ -297,11 +331,6 @@
     SB_DCHECK(force_secure_pipeline_under_tunnel_mode);
     *force_secure_pipeline_under_tunnel_mode = false;
 
-    if (!kTunnelModeEnabled) {
-      SB_LOG(INFO) << "Tunnel mode is disabled globally.";
-      return false;
-    }
-
     if (!SbAudioSinkIsAudioSampleTypeSupported(
             kSbMediaAudioSampleTypeInt16Deprecated)) {
       SB_LOG(INFO) << "Disable tunnel mode because int16 sample is required "
@@ -341,8 +370,9 @@
 
     bool is_encrypted = !!j_media_crypto;
     if (env->CallStaticBooleanMethodOrAbort(
-            "dev/cobalt/media/MediaCodecUtil", "hasTunneledCapableDecoder",
-            "(Ljava/lang/String;Z)Z", j_mime.Get(), is_encrypted) == JNI_TRUE) {
+            "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
+            "(Ljava/lang/String;ZIIIIZZ)Z", j_mime.Get(), is_encrypted, 0, 0, 0,
+            0, false, true) == JNI_TRUE) {
       return true;
     }
 
@@ -350,8 +380,9 @@
       const bool kIsEncrypted = true;
       auto support_tunnel_mode_under_secure_pipeline =
           env->CallStaticBooleanMethodOrAbort(
-              "dev/cobalt/media/MediaCodecUtil", "hasTunneledCapableDecoder",
-              "(Ljava/lang/String;Z)Z", j_mime.Get(), kIsEncrypted) == JNI_TRUE;
+              "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
+              "(Ljava/lang/String;ZIIIIZZ)Z", j_mime.Get(), kIsEncrypted, 0, 0,
+              0, 0, false, true) == JNI_TRUE;
       if (support_tunnel_mode_under_secure_pipeline) {
         *force_secure_pipeline_under_tunnel_mode = true;
         return true;
diff --git a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
index c8a3272..247d19f 100644
--- a/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/src/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -435,6 +435,8 @@
     ├── installation_store_<APP_KEY>.pb
     └── icu (default location shared by installation slots, to be explained below)
 ```
+Note that after the Cobalt binary is loaded by the loader_app, `kSbSystemPathContentDirectory` points to the
+content directory of the running binary, as stated in Starboard Module Reference of system.h.
 
 #### App metadata
 Each Cobalt Evergreen application has a set of unique metadata to track slot
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index a3c0d50..c931500 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -506,7 +506,7 @@
     double playback_rate;
     auto media_time = media_time_provider_->GetCurrentMediaTime(
         &is_playing, &is_eos_played, &is_underflow, &playback_rate);
-    update_media_info_cb_(media_time, dropped_frames, is_underflow);
+    update_media_info_cb_(media_time, dropped_frames, !is_underflow);
   }
 
   update_job_token_ = Schedule(update_job_, kUpdateInterval);
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc
index c273223..5a6b20b 100644
--- a/src/starboard/shared/starboard/player/player_internal.cc
+++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -29,9 +29,9 @@
 using std::placeholders::_3;
 using std::placeholders::_4;
 
-SbTime GetMediaTime(SbTime media_time,
-                    SbTimeMonotonic media_time_update_time,
-                    double playback_rate) {
+SbTime CalculateMediaTime(SbTime media_time,
+                          SbTimeMonotonic media_time_update_time,
+                          double playback_rate) {
   SbTimeMonotonic elapsed = SbTimeGetMonotonicNow() - media_time_update_time;
   return media_time + static_cast<SbTime>(elapsed * playback_rate);
 }
@@ -61,9 +61,8 @@
   worker_ = starboard::make_scoped_ptr(PlayerWorker::CreateInstance(
       audio_codec, video_codec, player_worker_handler.Pass(),
       std::bind(&SbPlayerPrivate::UpdateMediaInfo, this, _1, _2, _3, _4),
-      decoder_status_func, player_status_func,
-      player_error_func,
-      this, context));
+      decoder_status_func, player_status_func, player_error_func, this,
+      context));
 
   ++number_of_players_;
   SB_DLOG(INFO) << "Creating SbPlayerPrivate. There are " << number_of_players_
@@ -83,9 +82,8 @@
     starboard::scoped_ptr<PlayerWorker::Handler> player_worker_handler) {
   SbPlayerPrivate* ret = new SbPlayerPrivate(
       audio_codec, video_codec, audio_sample_info, sample_deallocate_func,
-      decoder_status_func, player_status_func,
-      player_error_func,
-      context, player_worker_handler.Pass());
+      decoder_status_func, player_status_func, player_error_func, context,
+      player_worker_handler.Pass());
 
   if (ret && ret->worker_) {
     return ret;
@@ -100,6 +98,7 @@
     SB_DCHECK(ticket_ != ticket);
     media_time_ = seek_to_time;
     media_time_updated_at_ = SbTimeGetMonotonicNow();
+    is_progressing_ = false;
     ticket_ = ticket;
   }
 
@@ -159,11 +158,11 @@
 
   starboard::ScopedLock lock(mutex_);
   out_player_info->duration = SB_PLAYER_NO_DURATION;
-  if (is_paused_ || underflow_) {
+  if (is_paused_ || !is_progressing_) {
     out_player_info->current_media_timestamp = media_time_;
   } else {
     out_player_info->current_media_timestamp =
-        GetMediaTime(media_time_, media_time_updated_at_, playback_rate_);
+        CalculateMediaTime(media_time_, media_time_updated_at_, playback_rate_);
   }
 
   out_player_info->frame_width = frame_width_;
@@ -194,13 +193,13 @@
 void SbPlayerPrivate::UpdateMediaInfo(SbTime media_time,
                                       int dropped_video_frames,
                                       int ticket,
-                                      bool underflow) {
+                                      bool is_progressing) {
   starboard::ScopedLock lock(mutex_);
   if (ticket_ != ticket) {
     return;
   }
   media_time_ = media_time;
-  underflow_ = underflow;
+  is_progressing_ = is_progressing;
   media_time_updated_at_ = SbTimeGetMonotonicNow();
   dropped_video_frames_ = dropped_video_frames;
 }
diff --git a/src/starboard/shared/starboard/player/player_internal.h b/src/starboard/shared/starboard/player/player_internal.h
index 4a94236..559531f 100644
--- a/src/starboard/shared/starboard/player/player_internal.h
+++ b/src/starboard/shared/starboard/player/player_internal.h
@@ -82,7 +82,7 @@
   void UpdateMediaInfo(SbTime media_time,
                        int dropped_video_frames,
                        int ticket,
-                       bool underflow);
+                       bool is_progressing);
 
   SbPlayerDeallocateSampleFunc sample_deallocate_func_;
   void* context_;
@@ -101,7 +101,9 @@
   double volume_ = 1.0;
   int total_video_frames_ = 0;
   int dropped_video_frames_ = 0;
-  bool underflow_ = false;
+  // Used to determine if |worker_| is progressing with playback so that
+  // we may extrapolate the media time in GetInfo().
+  bool is_progressing_ = false;
 
   starboard::scoped_ptr<PlayerWorker> worker_;
 
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index c4f3110..1a1a557 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -68,12 +68,10 @@
     SbPlayerErrorFunc player_error_func,
     SbPlayer player,
     void* context) {
-
-  PlayerWorker* ret = new PlayerWorker(audio_codec, video_codec, handler.Pass(),
-                                       update_media_info_cb,
-                                       decoder_status_func, player_status_func,
-                                       player_error_func,
-                                       player, context);
+  PlayerWorker* ret =
+      new PlayerWorker(audio_codec, video_codec, handler.Pass(),
+                       update_media_info_cb, decoder_status_func,
+                       player_status_func, player_error_func, player, context);
 
   if (ret && SbThreadIsValid(ret->thread_)) {
     return ret;
@@ -139,8 +137,9 @@
 
 void PlayerWorker::UpdateMediaInfo(SbTime time,
                                    int dropped_video_frames,
-                                   bool underflow) {
-  update_media_info_cb_(time, dropped_video_frames, ticket_, underflow);
+                                   bool is_progressing) {
+  SB_DCHECK(player_state_ == kSbPlayerStatePresenting);
+  update_media_info_cb_(time, dropped_video_frames, ticket_, is_progressing);
 }
 
 void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) {
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h
index 6c70552..3539c31 100644
--- a/src/starboard/shared/starboard/player/player_worker.h
+++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -46,7 +46,7 @@
   typedef std::function<void(SbTime media_time,
                              int dropped_video_frames,
                              int ticket,
-                             bool underflow)>
+                             bool is_progressing)>
       UpdateMediaInfoCB;
 
   struct Bounds {
@@ -61,7 +61,7 @@
   class Handler {
    public:
     typedef std::function<
-        void(SbTime media_time, int dropped_video_frames, bool underflow)>
+        void(SbTime media_time, int dropped_video_frames, bool is_progressing)>
         UpdateMediaInfoCB;
     typedef std::function<SbPlayerState()> GetPlayerStateCB;
     typedef std::function<void(SbPlayerState player_state)> UpdatePlayerStateCB;