diff options
49 files changed, 1768 insertions, 200 deletions
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl index 830591d5b503..a81eef831f4e 100644 --- a/core/java/android/view/IPinnedStackController.aidl +++ b/core/java/android/view/IPinnedStackController.aidl @@ -30,4 +30,9 @@ interface IPinnedStackController { * Notifies the controller that the user is currently interacting with the PIP. */ oneway void setInInteractiveMode(boolean inInteractiveMode); + + /** + * Notifies the controller that the desired snap mode is to the closest edge. + */ + oneway void setSnapToEdge(boolean snapToEdge); } diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index 45b7b0183fd8..cbacf269a0f0 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -46,7 +46,8 @@ public class PipSnapAlgorithm { private final Context mContext; private final ArrayList<Integer> mSnapGravities = new ArrayList<>(); - private final int mSnapMode = SNAP_MODE_CORNERS_ONLY; + private final int mDefaultSnapMode = SNAP_MODE_CORNERS_ONLY; + private int mSnapMode = mDefaultSnapMode; private Scroller mScroller; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -65,6 +66,13 @@ public class PipSnapAlgorithm { } /** + * Enables snapping to the closest edge. + */ + public void setSnapToEdge(boolean snapToEdge) { + mSnapMode = snapToEdge ? SNAP_MODE_EDGE : mDefaultSnapMode; + } + + /** * @return the closest absolute snap stack bounds for the given {@param stackBounds} moving at * the given {@param velocityX} and {@param velocityY}. The {@param movementBounds} should be * those for the given {@param stackBounds}. diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index a47062e4c828..a489421bd7ed 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -806,7 +806,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { std::unique_ptr<SkBitmap> bitmap(new SkBitmap); if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, - isSRGB ? SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) { + isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) { return NULL; } @@ -921,7 +921,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle); bitmapWrapper->getSkBitmap(&bitmap); - sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); bool isSRGB = bitmap.colorSpace() == sRGB.get(); p->writeInt32(isMutable); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index dffb63c774a6..322eed572940 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -437,7 +437,7 @@ android::Bitmap* GraphicsJNI::mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap, sk_sp<SkColorSpace> GraphicsJNI::defaultColorSpace() { #ifdef ANDROID_ENABLE_LINEAR_BLENDING - return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + return SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); #else return nullptr; #endif diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 7c05b7a8e362..d13d15468825 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -62,4 +62,7 @@ ListView/GridView are notably absent since this is their default anyway. Set to true for watch devices. --> <bool name="config_focusScrollContainersInTouchMode">true</bool> + + <!-- The small screens of watch devices makes multi-window support undesireable. --> + <bool name="config_supportsMultiWindow">false</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0678dfd1e759..6dc3f3eb1614 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2175,9 +2175,9 @@ --> <string-array translatable="false" name="config_telephonyHardware"> <!-- modem --> - <item>"0,modem,0,0,0,1,1,1"</item> + <item>0,modem,0,0,0,1,1,1</item> <!-- sim --> - <item>"1,sim,0,modem"</item> + <item>1,sim,0,modem</item> </string-array> <!-- This string array can be overriden to add an additional DRM support for WebView EME. --> diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 461936aaa493..e1b7788801d4 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -23,7 +23,10 @@ hwui_src_files := \ pipeline/skia/RenderNodeDrawable.cpp \ pipeline/skia/ReorderBarrierDrawables.cpp \ pipeline/skia/SkiaDisplayList.cpp \ + pipeline/skia/SkiaOpenGLPipeline.cpp \ + pipeline/skia/SkiaPipeline.cpp \ pipeline/skia/SkiaRecordingCanvas.cpp \ + pipeline/skia/SkiaVulkanPipeline.cpp \ renderstate/Blend.cpp \ renderstate/MeshState.cpp \ renderstate/OffscreenBufferPool.cpp \ @@ -296,6 +299,7 @@ LOCAL_SRC_FILES += \ tests/unit/RenderPropertiesTests.cpp \ tests/unit/SkiaBehaviorTests.cpp \ tests/unit/SkiaDisplayListTests.cpp \ + tests/unit/SkiaPipelineTests.cpp \ tests/unit/SkiaCanvasTests.cpp \ tests/unit/SnapshotTests.cpp \ tests/unit/StringUtilsTests.cpp \ diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 4cfbb2a43198..700642ed7334 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -41,6 +41,13 @@ void DeviceInfo::initialize() { }); } +void DeviceInfo::initialize(int maxTextureSize) { + std::call_once(sInitializedFlag, [maxTextureSize]() { + sDeviceInfo = new DeviceInfo(); + sDeviceInfo->mMaxTextureSize = maxTextureSize; + }); +} + void DeviceInfo::load() { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); } diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index e551eb9bb8c0..aff84b02d85a 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -32,6 +32,7 @@ public: // only call this after GL has been initialized, or at any point if compiled // with HWUI_NULL_GPU static void initialize(); + static void initialize(int maxTextureSize); int maxTextureSize() const { return mMaxTextureSize; } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 3819c5e9187d..b8964f0f16a0 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -33,6 +33,7 @@ #include "Matrix.h" #include "RenderProperties.h" #include "pipeline/skia/SkiaDisplayList.h" +#include "pipeline/skia/SkiaLayer.h" #include <vector> @@ -60,10 +61,6 @@ namespace proto { class RenderNode; } -namespace skiapipeline { - class SkiaDisplayList; -} - /** * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties. * @@ -312,14 +309,23 @@ public: * Returns true if an offscreen layer from any renderPipeline is attached * to this node. */ - bool hasLayer() const { return mLayer || mLayerSurface.get(); } + bool hasLayer() const { return mLayer || mSkiaLayer.get(); } /** * Used by the RenderPipeline to attach an offscreen surface to the RenderNode. * The surface is then will be used to store the contents of a layer. */ - void setLayerSurface(sk_sp<SkSurface> layer) { mLayerSurface = layer; } - + void setLayerSurface(sk_sp<SkSurface> layer) { + if (layer.get()) { + if (!mSkiaLayer.get()) { + mSkiaLayer = std::make_unique<skiapipeline::SkiaLayer>(); + } + mSkiaLayer->layerSurface = std::move(layer); + mSkiaLayer->inverseTransformInWindow.loadIdentity(); + } else { + mSkiaLayer.reset(); + } + } /** * If the RenderNode is of type LayerType::RenderLayer then this method will @@ -330,7 +336,13 @@ public: * NOTE: this function is only guaranteed to return accurate results after * prepareTree has been run for this RenderNode */ - SkSurface* getLayerSurface() const { return mLayerSurface.get(); } + SkSurface* getLayerSurface() const { + return mSkiaLayer.get() ? mSkiaLayer->layerSurface.get() : nullptr; + } + + skiapipeline::SkiaLayer* getSkiaLayer() const { + return mSkiaLayer.get(); + } private: /** @@ -346,7 +358,7 @@ private: * An offscreen rendering target used to contain the contents this RenderNode * when it has been set to draw as a LayerType::RenderLayer. */ - sk_sp<SkSurface> mLayerSurface; + std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer; }; // class RenderNode } /* namespace uirenderer */ diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index f67408698426..5978abc9efaa 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -27,6 +27,7 @@ #include <SkRRect.h> #include <SkRSXform.h> #include <SkSurface.h> +#include <SkTextBlobRunIterator.h> #include <memory> @@ -371,7 +372,42 @@ void SkiaCanvasProxy::onDrawTextRSXform(const void* text, size_t byteLength, void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) { - SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextBlob is not supported"); + SkPaint runPaint = paint; + + SkTextBlobRunIterator it(blob); + for (;!it.done(); it.next()) { + size_t textLen = it.glyphCount() * sizeof(uint16_t); + const SkPoint& offset = it.offset(); + // applyFontToPaint() always overwrites the exact same attributes, + // so it is safe to not re-seed the paint for this reason. + it.applyFontToPaint(&runPaint); + + switch (it.positioning()) { + case SkTextBlob::kDefault_Positioning: + this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint); + break; + case SkTextBlob::kHorizontal_Positioning: { + std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); + for (size_t i = 0; i < it.glyphCount(); i++) { + pts[i].set(x + offset.x() + it.pos()[i], y + offset.y()); + } + this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); + break; + } + case SkTextBlob::kFull_Positioning: { + std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); + for (size_t i = 0; i < it.glyphCount(); i++) { + const size_t xIndex = i*2; + const size_t yIndex = xIndex + 1; + pts[i].set(x + offset.x() + it.pos()[xIndex], y + offset.y() + it.pos()[yIndex]); + } + this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); + break; + } + default: + SkFAIL("unhandled positioning mode"); + } + } } void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 0a60a8ea4c99..5b5b74e1c3f3 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -272,7 +272,7 @@ void Texture::upload(Bitmap& bitmap) { setDefaultParams = true; } - sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); bool needSRGB = bitmap.info().colorSpace() == sRGB.get(); GLint internalFormat, format, type; diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index b50647adc0be..97b7dd722e08 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -564,7 +564,7 @@ bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) { #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else - sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); #endif SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); cache.bitmap = Bitmap::allocateHeapBitmap(info); diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index be0b22ee8d31..b8f7d9f01697 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -217,7 +217,7 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr return nullptr; } - sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); bool needSRGB = skBitmap.info().colorSpace() == sRGB.get(); bool hasSRGB = caches.extensions().hasSRGB(); GLint format, type, internalFormat; @@ -307,7 +307,7 @@ void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, SkColorTab } mRowBytes = rowBytes; if (mColorTable.get() != ctable) { - mColorTable.reset(ctable); + mColorTable.reset(SkSafeRef(ctable)); } // Need to validate the alpha type to filter against the color type @@ -482,4 +482,4 @@ GraphicBuffer* Bitmap::graphicBuffer() { return nullptr; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 1ea8bd2fcdbf..2dc8ce8fe774 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -20,6 +20,8 @@ #include "RenderNode.h" #include "MinikinUtils.h" #include "Paint.h" +#include "Properties.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" #include "Typeface.h" #include <SkDrawFilter.h> @@ -27,6 +29,9 @@ namespace android { Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) { + if (uirenderer::Properties::isSkiaEnabled()) { + return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height); + } return new uirenderer::RecordingCanvas(width, height); } diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index b656d6737ab3..b971856c6e73 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -17,7 +17,7 @@ #include "RenderNodeDrawable.h" #include "RenderNode.h" #include "SkiaDisplayList.h" -#include "SkiaFrameRenderer.h" +#include "SkiaPipeline.h" #include "utils/TraceUtils.h" namespace android { @@ -57,7 +57,7 @@ void RenderNodeDrawable::onDraw(SkCanvas* canvas) { void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { RenderNode* renderNode = mRenderNode.get(); - if (SkiaFrameRenderer::skpCaptureEnabled()) { + if (SkiaPipeline::skpCaptureEnabled()) { SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight()); canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr); } diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index ac185878b9ca..a204d5cb8f71 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -17,7 +17,7 @@ #include "ReorderBarrierDrawables.h" #include "RenderNode.h" #include "SkiaDisplayList.h" -#include "SkiaFrameRenderer.h" +#include "SkiaPipeline.h" #include <SkBlurMask.h> #include <SkBlurMaskFilter.h> @@ -161,7 +161,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca return; } - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -170,7 +170,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca zRatio = 0.95f; } - float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio; + float blurRadius = SkiaPipeline::getLightRadius()*zRatio; SkAutoCanvasRestore acr(canvas, true); @@ -276,7 +276,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi } if (spotAlpha > 0.0f) { - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -285,7 +285,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi zRatio = 0.95f; } - const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius(); + const SkScalar lightWidth = SkiaPipeline::getLightRadius(); SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio; // the device-space radius sent to the blur shader must fit in 14.2 fixed point if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) { @@ -439,7 +439,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo } if (spotAlpha > 0.0f) { - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -448,7 +448,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo zRatio = 0.95f; } - const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius(); + const SkScalar lightWidth = SkiaPipeline::getLightRadius(); const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio; const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor; @@ -626,8 +626,8 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* return; } - float ambientAlpha = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha; - float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha; + float ambientAlpha = SkiaPipeline::getAmbientShadowAlpha()*casterAlpha; + float spotAlpha = SkiaPipeline::getSpotShadowAlpha()*casterAlpha; const float casterZValue = casterProperties.getZ(); const RevealClip& revealClip = casterProperties.getRevealClip(); diff --git a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h b/libs/hwui/pipeline/skia/SkiaLayer.h index 70207c1280ab..0988d7eda89d 100644 --- a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h +++ b/libs/hwui/pipeline/skia/SkiaLayer.h @@ -16,39 +16,23 @@ #pragma once +#include <SkSurface.h> +#include "Matrix.h" + namespace android { namespace uirenderer { namespace skiapipeline { /** - * TODO: this is a stub that will be added in a subsquent CL + * An offscreen rendering target used to contain the contents a RenderNode. */ -class SkiaFrameRenderer { -public: - - static bool skpCaptureEnabled() { return false; } - - // TODO avoids unused compile error but we need to pass this to the reorder drawables! - static float getLightRadius() { - return 1.0f; - } - - static uint8_t getAmbientShadowAlpha() { - return 1; - } - - static uint8_t getSpotShadowAlpha() { - return 1; - } - - static Vector3 getLightCenter() { - Vector3 result; - result.x = result.y = result.z = 1.0f; - return result; - } - +struct SkiaLayer +{ + sk_sp<SkSurface> layerSurface; + Matrix4 inverseTransformInWindow; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp new file mode 100644 index 000000000000..519de967794d --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "SkiaOpenGLPipeline.h" + +#include "DeferredLayerUpdater.h" +#include "renderthread/EglManager.h" +#include "renderstate/RenderState.h" +#include "Readback.h" +#include "utils/TraceUtils.h" + +#include <android/native_window.h> +#include <cutils/properties.h> +#include <strings.h> + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread) + : SkiaPipeline(thread) + , mEglManager(thread.eglManager()) { +} + +MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { + // TODO: Figure out why this workaround is needed, see b/13913604 + // In the meantime this matches the behavior of GLRenderer, so it is not a regression + EGLint error = 0; + if (!mEglManager.makeCurrent(mEglSurface, &error)) { + return MakeCurrentResult::AlreadyCurrent; + } + return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; +} + +Frame SkiaOpenGLPipeline::getFrame() { + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); +} + +bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, + const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, + FrameInfoVisualizer* profiler) { + + mEglManager.damageFrame(frame, dirty); + + // setup surface for fbo0 + GrBackendRenderTargetDesc renderTargetDesc; + renderTargetDesc.fWidth = frame.width(); + renderTargetDesc.fHeight = frame.height(); + renderTargetDesc.fConfig = kRGBA_8888_GrPixelConfig; + renderTargetDesc.fOrigin = kBottomLeft_GrSurfaceOrigin; + renderTargetDesc.fSampleCnt = 0; + renderTargetDesc.fStencilBits = STENCIL_BUFFER_SIZE; + renderTargetDesc.fRenderTargetHandle = 0; + + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + + SkASSERT(mRenderThread.getGrContext() != nullptr); + sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( + mRenderThread.getGrContext(), renderTargetDesc, &props)); + + SkiaPipeline::updateLighting(lightGeometry, lightInfo); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + layerUpdateQueue->clear(); + return true; +} + +bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { + + GL_CHECKPOINT(LOW); + + // Even if we decided to cancel the frame, from the perspective of jank + // metrics the frame was swapped at this point + currentFrameInfo->markSwapBuffers(); + + *requireSwap = drew || mEglManager.damageRequiresSwap(); + + if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { + return false; + } + + return *requireSwap; +} + +bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { + layer->apply(); + return Readback::copyTextureLayerInto(mRenderThread, *(layer->backingLayer()), bitmap) + == CopyResult::Success; +} + +DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() { + mEglManager.initialize(); + Layer* layer = new Layer(mRenderThread.renderState(), 0, 0); + layer->generateTexture(); + return new DeferredLayerUpdater(layer); +} + +void SkiaOpenGLPipeline::onStop() { + if (mEglManager.isCurrent(mEglSurface)) { + mEglManager.makeCurrent(EGL_NO_SURFACE); + } +} + +bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) { + + if (mEglSurface != EGL_NO_SURFACE) { + mEglManager.destroySurface(mEglSurface); + mEglSurface = EGL_NO_SURFACE; + } + + if (surface) { + mEglSurface = mEglManager.createSurface(surface); + } + + if (mEglSurface != EGL_NO_SURFACE) { + const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); + mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); + return true; + } + + return false; +} + +bool SkiaOpenGLPipeline::isSurfaceReady() { + return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); +} + +bool SkiaOpenGLPipeline::isContextReady() { + return CC_LIKELY(mEglManager.hasEglContext()); +} + +void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { + DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; + if (thread.eglManager().hasEglContext()) { + mode = DrawGlInfo::kModeProcess; + } + + (*functor)(mode, nullptr); + + // If there's no context we don't need to reset as there's no gl state to save/restore + if (mode != DrawGlInfo::kModeProcessNoContext) { + thread.getGrContext()->resetContext(); + } +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h new file mode 100644 index 000000000000..36685ddb17a7 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SkiaPipeline.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaOpenGLPipeline : public SkiaPipeline { +public: + SkiaOpenGLPipeline(renderthread::RenderThread& thread); + virtual ~SkiaOpenGLPipeline() {} + + renderthread::MakeCurrentResult makeCurrent() override; + renderthread::Frame getFrame() override; + bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector< sp<RenderNode> >& renderNodes, + FrameInfoVisualizer* profiler) override; + bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, + FrameInfo* currentFrameInfo, bool* requireSwap) override; + bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; + DeferredLayerUpdater* createTextureLayer() override; + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override; + void onStop() override; + bool isSurfaceReady() override; + bool isContextReady() override; + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); + +private: + renderthread::EglManager& mEglManager; + EGLSurface mEglSurface = EGL_NO_SURFACE; + bool mBufferPreserved = false; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp new file mode 100644 index 000000000000..03fa266a5c20 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "SkiaPipeline.h" + +#include "utils/TraceUtils.h" +#include <SkOSFile.h> +#include <SkPicture.h> +#include <SkPictureRecorder.h> +#include <SkPixelSerializer.h> +#include <SkStream.h> + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +float SkiaPipeline::mLightRadius = 0; +uint8_t SkiaPipeline::mAmbientShadowAlpha = 0; +uint8_t SkiaPipeline::mSpotShadowAlpha = 0; + +Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN}; + +SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { } + +TaskManager* SkiaPipeline::getTaskManager() { + return &mTaskManager; +} + +void SkiaPipeline::onDestroyHardwareResources() { + // No need to flush the caches here. There is a timer + // which will flush temporary resources over time. +} + +void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo) { + updateLighting(lightGeometry, lightInfo); + ATRACE_NAME("draw layers"); + renderLayersImpl(*layerUpdateQueue, opaque); + layerUpdateQueue->clear(); +} + +void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { + // Render all layers that need to be updated, in order. + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode; + // only schedule repaint if node still on layer - possible it may have been + // removed during a dropped frame, but layers may still remain scheduled so + // as not to lose info on what portion is damaged + if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) { + SkASSERT(layerNode->getLayerSurface()); + SkASSERT(layerNode->getDisplayList()->isSkiaDL()); + SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); + if (!displayList || displayList->isEmpty()) { + SkDEBUGF(("%p drawLayers(%s) : missing drawable", this, layerNode->getName())); + return; + } + + const Rect& layerDamage = layers.entries()[i].damage; + + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); + + int saveCount = layerCanvas->save(); + SkASSERT(saveCount == 1); + + layerCanvas->clipRect(layerDamage.toSkRect(), SkRegion::kReplace_Op); + + auto savedLightCenter = mLightCenter; + // map current light center into RenderNode's coordinate space + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(mLightCenter); + + const RenderProperties& properties = layerNode->properties(); + const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { + return; + } + + layerCanvas->clear(SK_ColorTRANSPARENT); + + RenderNodeDrawable root(layerNode, layerCanvas, false); + root.forceDraw(layerCanvas); + layerCanvas->restoreToCount(saveCount); + layerCanvas->flush(); + mLightCenter = savedLightCenter; + } + } +} + +bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator) { + SkSurface* layer = node->getLayerSurface(); + if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) { + SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight()); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkASSERT(mRenderThread.getGrContext() != nullptr); + node->setLayerSurface( + SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + info, 0, &props)); + if (node->getLayerSurface()) { + // update the transform in window of the layer to reset its origin wrt light source + // position + Matrix4 windowTransform; + damageAccumulator.computeCurrentTransform(&windowTransform); + node->getSkiaLayer()->inverseTransformInWindow = windowTransform; + } + return true; + } + return false; +} + +void SkiaPipeline::destroyLayer(RenderNode* node) { + node->setLayerSurface(nullptr); +} + +void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { + GrContext* context = thread.getGrContext(); + if (context) { + ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); + SkBitmap skiaBitmap; + bitmap->getSkBitmap(&skiaBitmap); + sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); + SkImage_pinAsTexture(image.get(), context); + SkImage_unpinAsTexture(image.get(), context); + } +} + +// Encodes to PNG, unless there is already encoded data, in which case that gets +// used. +class PngPixelSerializer : public SkPixelSerializer { +public: + bool onUseEncodedData(const void*, size_t) override { return true; } + SkData* onEncode(const SkPixmap& pixmap) override { + return SkImageEncoder::EncodeData(pixmap.info(), pixmap.addr(), pixmap.rowBytes(), + SkImageEncoder::kPNG_Type, 100); + } +}; + +void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, + const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, + sk_sp<SkSurface> surface) { + + // unpin all mutable images that were attached to nodes deleted while on the UI thread + SkiaDisplayList::cleanupImages(surface->getCanvas()->getGrContext()); + + // draw all layers up front + renderLayersImpl(layers, opaque); + + // initialize the canvas for the current frame + SkCanvas* canvas = surface->getCanvas(); + + std::unique_ptr<SkPictureRecorder> recorder; + bool recordingPicture = false; + char prop[PROPERTY_VALUE_MAX]; + if (skpCaptureEnabled()) { + property_get("debug.hwui.capture_frame_as_skp", prop, "0"); + recordingPicture = prop[0] != '0' && !sk_exists(prop); + if (recordingPicture) { + recorder.reset(new SkPictureRecorder()); + canvas = recorder->beginRecording(surface->width(), surface->height(), + nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + } + } + + canvas->clipRect(clip, SkRegion::kReplace_Op); + + if (!opaque) { + canvas->clear(SK_ColorTRANSPARENT); + } + + // If there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) + // #2 - additional overlay nodes + // Usually the backdrop cannot be seen since it will be entirely covered by the content. While + // resizing however it might become partially visible. The following render loop will crop the + // backdrop against the content and draw the remaining part of it. It will then draw the content + // cropped to the backdrop (since that indicates a shrinking of the window). + // + // Additional nodes will be drawn on top with no particular clipping semantics. + + // The bounds of the backdrop against which the content should be clipped. + Rect backdropBounds = contentDrawBounds; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + // If there is no content bounds we ignore the layering as stated above and start with 2. + int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0; + + for (const sp<RenderNode>& node : nodes) { + if (node->nothingToDraw()) continue; + + SkASSERT(node->getDisplayList()->isSkiaDL()); + + int count = canvas->save(); + + if (layer == 0) { + const RenderProperties& properties = node->properties(); + Rect targetBounds(properties.getLeft(), properties.getTop(), + properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + // Remember the intersection of the target bounds and the intersection bounds against + // which we have to crop the content. + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); + backdropBounds.doIntersect(targetBounds); + } else if (layer == 1) { + // We shift and clip the content to match its final location in the window. + const SkRect clip = SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top, + backdropBounds.getWidth(), backdropBounds.getHeight()); + const float dx = backdropBounds.left - contentDrawBounds.left; + const float dy = backdropBounds.top - contentDrawBounds.top; + canvas->translate(dx, dy); + // It gets cropped against the bounds of the backdrop to stay inside. + canvas->clipRect(clip, SkRegion::kIntersect_Op); + } + + RenderNodeDrawable root(node.get(), canvas); + root.draw(canvas); + canvas->restoreToCount(count); + layer++; + } + + if (skpCaptureEnabled() && recordingPicture) { + sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture(); + if (picture->approximateOpCount() > 0) { + SkFILEWStream stream(prop); + if (stream.isValid()) { + PngPixelSerializer serializer; + picture->serialize(&stream, &serializer); + stream.flush(); + SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop); + } + } + surface->getCanvas()->drawPicture(picture); + } + + ATRACE_NAME("flush commands"); + canvas->flush(); +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h new file mode 100644 index 000000000000..160046af63a6 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "renderthread/CanvasContext.h" +#include "FrameBuilder.h" +#include "renderthread/IRenderPipeline.h" +#include <SkSurface.h> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaPipeline : public renderthread::IRenderPipeline { +public: + SkiaPipeline(renderthread::RenderThread& thread); + virtual ~SkiaPipeline() {} + + TaskManager* getTaskManager() override; + + void onDestroyHardwareResources() override; + + void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo) override; + + bool createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator) override; + + void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, + const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds, + sk_sp<SkSurface> surface); + + static void destroyLayer(RenderNode* node); + + static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); + + static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); + + static bool skpCaptureEnabled() { return false; } + + static float getLightRadius() { + if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) { + return Properties::overrideLightRadius; + } + return mLightRadius; + } + + static uint8_t getAmbientShadowAlpha() { + if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { + return Properties::overrideAmbientShadowStrength; + } + return mAmbientShadowAlpha; + } + + static uint8_t getSpotShadowAlpha() { + if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { + return Properties::overrideSpotShadowStrength; + } + return mSpotShadowAlpha; + } + + static Vector3 getLightCenter() { + if (CC_UNLIKELY(Properties::overrideLightPosY > 0 || Properties::overrideLightPosZ > 0)) { + Vector3 adjustedLightCenter = mLightCenter; + if (CC_UNLIKELY(Properties::overrideLightPosY > 0)) { + // negated since this shifts up + adjustedLightCenter.y = - Properties::overrideLightPosY; + } + if (CC_UNLIKELY(Properties::overrideLightPosZ > 0)) { + adjustedLightCenter.z = Properties::overrideLightPosZ; + } + return adjustedLightCenter; + } + return mLightCenter; + } + + static void updateLighting(const FrameBuilder::LightGeometry& lightGeometry, + const BakedOpRenderer::LightInfo& lightInfo) { + mLightRadius = lightGeometry.radius; + mAmbientShadowAlpha = lightInfo.ambientShadowAlpha; + mSpotShadowAlpha = lightInfo.spotShadowAlpha; + mLightCenter = lightGeometry.center; + } +protected: + renderthread::RenderThread& mRenderThread; + +private: + TaskManager mTaskManager; + static float mLightRadius; + static uint8_t mAmbientShadowAlpha; + static uint8_t mSpotShadowAlpha; + static Vector3 mLightCenter; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp new file mode 100644 index 000000000000..520309a50f53 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "SkiaVulkanPipeline.h" + +#include "DeferredLayerUpdater.h" +#include "renderthread/EglManager.h" // needed for Frame +#include "Readback.h" +#include "renderstate/RenderState.h" + +#include <SkTypes.h> +#include <WindowContextFactory_android.h> +#include <VulkanWindowContext.h> + +#include <android/native_window.h> +#include <cutils/properties.h> +#include <strings.h> + +using namespace android::uirenderer::renderthread; +using namespace sk_app; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { + return (mWindowContext != nullptr) ? + MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; +} + +Frame SkiaVulkanPipeline::getFrame() { + LOG_ALWAYS_FATAL_IF(mWindowContext == nullptr, "Tried to draw into null vulkan context!"); + mBackbuffer = mWindowContext->getBackbufferSurface(); + if (mBackbuffer.get() == nullptr) { + // try recreating the context? + SkDebugf("failed to get backbuffer"); + return Frame(-1, -1, 0); + } + + // TODO: support buffer age if Vulkan API can do it + Frame frame(mBackbuffer->width(), mBackbuffer->height(), 0); + return frame; +} + +bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, + const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, + FrameInfoVisualizer* profiler) { + + if (mBackbuffer.get() == nullptr) { + return false; + } + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mBackbuffer); + layerUpdateQueue->clear(); + return true; +} + +bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { + + *requireSwap = drew; + + // Even if we decided to cancel the frame, from the perspective of jank + // metrics the frame was swapped at this point + currentFrameInfo->markSwapBuffers(); + + if (*requireSwap) { + mWindowContext->swapBuffers(); + } + + mBackbuffer.reset(); + + return *requireSwap; +} + +bool SkiaVulkanPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { + // TODO: implement copyLayerInto for vulkan. + return false; +} + +DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { + Layer* layer = new Layer(mRenderThread.renderState(), 0, 0); + return new DeferredLayerUpdater(layer); +} + +void SkiaVulkanPipeline::onStop() { +} + +bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) { + + if (mWindowContext) { + delete mWindowContext; + mWindowContext = nullptr; + } + + if (surface) { + DisplayParams displayParams; + mWindowContext = window_context_factory::NewVulkanForAndroid(surface, displayParams); + if (mWindowContext) { + DeviceInfo::initialize(mWindowContext->getGrContext()->caps()->maxRenderTargetSize()); + } + } + + + // this doesn't work for if there is more than one CanvasContext available at one time! + mRenderThread.setGrContext(mWindowContext ? mWindowContext->getGrContext() : nullptr); + + return mWindowContext != nullptr; +} + +bool SkiaVulkanPipeline::isSurfaceReady() { + return CC_LIKELY(mWindowContext != nullptr) && mWindowContext->isValid(); +} + +bool SkiaVulkanPipeline::isContextReady() { + return CC_LIKELY(mWindowContext != nullptr); +} + +void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { + // TODO: we currently don't support OpenGL WebView's + DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; + (*functor)(mode, nullptr); +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h new file mode 100644 index 000000000000..cdc869245be3 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SkiaPipeline.h" +#include <SkSurface.h> + +namespace sk_app { +class WindowContext; +} + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaVulkanPipeline : public SkiaPipeline { +public: + SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} + virtual ~SkiaVulkanPipeline() {} + + renderthread::MakeCurrentResult makeCurrent() override; + renderthread::Frame getFrame() override; + bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector< sp<RenderNode> >& renderNodes, + FrameInfoVisualizer* profiler) override; + bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, + FrameInfo* currentFrameInfo, bool* requireSwap) override; + bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; + DeferredLayerUpdater* createTextureLayer() override; + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override; + void onStop() override; + bool isSurfaceReady() override; + bool isContextReady() override; + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); + +private: + sk_app::WindowContext* mWindowContext = nullptr; + sk_sp<SkSurface> mBackbuffer; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 03deb0ac213b..c561c86460c6 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -28,6 +28,9 @@ #include "renderstate/Stencil.h" #include "protos/hwui.pb.h" #include "OpenGLPipeline.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "pipeline/skia/SkiaPipeline.h" +#include "pipeline/skia/SkiaVulkanPipeline.h" #include "utils/GLUtils.h" #include "utils/TimeUtils.h" @@ -69,13 +72,11 @@ CanvasContext* CanvasContext::create(RenderThread& thread, return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_unique<OpenGLPipeline>(thread)); case RenderPipelineType::SkiaGL: - //TODO: implement SKIA GL - LOG_ALWAYS_FATAL("skiaGL canvas type not implemented."); - break; + return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, + std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread)); case RenderPipelineType::SkiaVulkan: - //TODO: implement Vulkan - LOG_ALWAYS_FATAL("Vulkan canvas type not implemented."); - break; + return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, + std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread)); default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -89,6 +90,10 @@ void CanvasContext::destroyLayer(RenderNode* node) { case RenderPipelineType::OpenGL: OpenGLPipeline::destroyLayer(node); break; + case RenderPipelineType::SkiaGL: + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaPipeline::destroyLayer(node); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -102,6 +107,12 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) case RenderPipelineType::OpenGL: OpenGLPipeline::invokeFunctor(thread, functor); break; + case RenderPipelineType::SkiaGL: + skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor); + break; + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaVulkanPipeline::invokeFunctor(thread, functor); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -114,6 +125,10 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { case RenderPipelineType::OpenGL: OpenGLPipeline::prepareToDraw(thread, bitmap); break; + case RenderPipelineType::SkiaGL: + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 7349dcc9f041..b12522e6bd41 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -31,6 +31,11 @@ class EglManager; class Frame { public: + Frame(EGLint width, EGLint height, EGLint bufferAge) + : mWidth(width) + , mHeight(height) + , mBufferAge(bufferAge) { } + EGLint width() const { return mWidth; } EGLint height() const { return mHeight; } @@ -39,6 +44,7 @@ public: EGLint bufferAge() const { return mBufferAge; } private: + Frame() {} friend class EglManager; EGLSurface mSurface; diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 0be5a3b9d439..0ce598d4dba4 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -22,6 +22,7 @@ #include <Rect.h> #include <RenderNode.h> #include <hwui/Bitmap.h> +#include <pipeline/skia/SkiaRecordingCanvas.h> #include <renderstate/RenderState.h> #include <renderthread/RenderThread.h> #include <Snapshot.h> @@ -196,6 +197,35 @@ public: node.setStagingDisplayList(canvas->finishRecording(), nullptr); } + static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom, + std::function<void(RenderProperties& props, skiapipeline::SkiaRecordingCanvas& canvas)> setup, + const char* name = nullptr, skiapipeline::SkiaDisplayList* displayList = nullptr) { + #if HWUI_NULL_GPU + // if RenderNodes are being sync'd/used, device info will be needed, since + // DeviceInfo::maxTextureSize() affects layer property + DeviceInfo::initialize(); + #endif + sp<RenderNode> node = new RenderNode(); + if (name) { + node->setName(name); + } + RenderProperties& props = node->mutateStagingProperties(); + props.setLeftTopRightBottom(left, top, right, bottom); + if (displayList) { + node->setStagingDisplayList(displayList, nullptr); + } + if (setup) { + std::unique_ptr<skiapipeline::SkiaRecordingCanvas> canvas( + new skiapipeline::SkiaRecordingCanvas(nullptr, + props.getWidth(), props.getHeight())); + setup(props, *canvas.get()); + node->setStagingDisplayList(canvas->finishRecording(), nullptr); + } + node->setPropertyFieldsDirty(0xFFFFFFFF); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + return node; + } + /** * Forces a sync of a tree of RenderNode, such that every descendant will have its staging * properties and DisplayList moved to the render copies. diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 19c311c7e182..c68ca4e74368 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -34,34 +34,6 @@ using namespace android::uirenderer; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::skiapipeline; -static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom, - std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup, - const char* name = nullptr, SkiaDisplayList* displayList = nullptr) { -#if HWUI_NULL_GPU - // if RenderNodes are being sync'd/used, device info will be needed, since - // DeviceInfo::maxTextureSize() affects layer property - DeviceInfo::initialize(); -#endif - sp<RenderNode> node = new RenderNode(); - if (name) { - node->setName(name); - } - RenderProperties& props = node->mutateStagingProperties(); - props.setLeftTopRightBottom(left, top, right, bottom); - if (displayList) { - node->setStagingDisplayList(displayList, nullptr); - } - if (setup) { - std::unique_ptr<SkiaRecordingCanvas> canvas(new SkiaRecordingCanvas(nullptr, - props.getWidth(), props.getHeight())); - setup(props, *canvas.get()); - node->setStagingDisplayList(canvas->finishRecording(), nullptr); - } - node->setPropertyFieldsDirty(0xFFFFFFFF); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - return node; -} - TEST(RenderNodeDrawable, create) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { @@ -86,7 +58,7 @@ TEST(RenderNodeDrawable, drawContent) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); //create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& recorder) { recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); @@ -118,14 +90,14 @@ TEST(RenderNodeDrawable, drawAndReorder) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); //-z draws to all 4 pixels (RED) - auto redNode = createSkiaNode(0, 0, 4, 4, + auto redNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); props.setElevation(-10.0f); }, "redNode"); //0z draws to bottom 2 pixels (GREEN) - auto bottomHalfGreenNode = createSkiaNode(0, 0, 4, 4, + auto bottomHalfGreenNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { SkPaint greenPaint; greenPaint.setColor(SK_ColorGREEN); @@ -135,7 +107,7 @@ TEST(RenderNodeDrawable, drawAndReorder) { }, "bottomHalfGreenNode"); //+z draws to right 2 pixels (BLUE) - auto rightHalfBlueNode = createSkiaNode(0, 0, 4, 4, + auto rightHalfBlueNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) { SkPaint bluePaint; bluePaint.setColor(SK_ColorBLUE); @@ -144,7 +116,7 @@ TEST(RenderNodeDrawable, drawAndReorder) { props.setElevation(10.0f); }, "rightHalfBlueNode"); - auto rootNode = createSkiaNode(0, 0, 4, 4, + auto rootNode = TestUtils::createSkiaNode(0, 0, 4, 4, [&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) { rootRecorder.insertReorderBarrier(true); //draw in reverse Z order, so Z alters draw order @@ -167,7 +139,7 @@ TEST(RenderNodeDrawable, composeOnLayer) canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& recorder) { recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); @@ -176,7 +148,7 @@ TEST(RenderNodeDrawable, composeOnLayer) auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1); auto canvas2 = surfaceLayer->getCanvas(); canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - rootNode->setLayerSurface( surfaceLayer ); + rootNode->setLayerSurface(surfaceLayer); RenderNodeDrawable drawable1(rootNode.get(), &canvas, false); canvas.drawDrawable(&drawable1); @@ -190,7 +162,7 @@ TEST(RenderNodeDrawable, composeOnLayer) canvas.drawDrawable(&drawable3); ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0)); - rootNode->setLayerSurface( sk_sp<SkSurface>() ); + rootNode->setLayerSurface(sk_sp<SkSurface>()); } //TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder @@ -203,18 +175,18 @@ TEST(RenderNodeDrawable, projectDraw) { canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - auto redNode = createSkiaNode(0, 0, 1, 1, + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }, "redNode"); - auto greenNodeWithRedChild = createSkiaNode(0, 0, 1, 1, + auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1, [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) { greenCanvasWithRedChild.drawRenderNode(redNode.get()); greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver); }, "greenNodeWithRedChild"); - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) { rootCanvas.drawRenderNode(greenNodeWithRedChild.get()); }, "rootNode"); diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index 3f80d6ec6577..f32d97a3d809 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -93,7 +93,7 @@ TEST(SkiaBehavior, porterDuffCreateIsCached) { } TEST(SkiaBehavior, srgbColorSpaceIsSingleton) { - sk_sp<SkColorSpace> sRGB1 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); - sk_sp<SkColorSpace> sRGB2 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB1 = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB2 = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); ASSERT_EQ(sRGB1.get(), sRGB2.get()); } diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp new file mode 100644 index 000000000000..7a2fa5792f67 --- /dev/null +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 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. + */ + +#include <gtest/gtest.h> +#include <VectorDrawable.h> + +#include "AnimationContext.h" +#include "DamageAccumulator.h" +#include "IContextFactory.h" +#include "pipeline/skia/SkiaDisplayList.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "renderthread/CanvasContext.h" +#include "tests/common/TestUtils.h" +#include "SkiaCanvas.h" +#include <SkLiteRecorder.h> +#include <string.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::skiapipeline; + +RENDERTHREAD_TEST(SkiaPipeline, renderFrame) { + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + bool opaque = true; + android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(1, 1); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckBounds) { + auto backdropRedNode = TestUtils::createSkiaNode(1, 1, 4, 4, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + auto contentGreenNode = TestUtils::createSkiaNode(2, 2, 5, 5, + [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { + blueCanvas.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(backdropRedNode); //first node is backdrop + renderNodes.push_back(contentGreenNode); //second node is content drawn on top of the backdrop + android::uirenderer::Rect contentDrawBounds(1, 1, 3, 3); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(5, 5); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + //backdropBounds is (1, 1, 3, 3), content clip is (1, 1, 3, 3), content translate is (0, 0) + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorGREEN); + ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); + + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + contentDrawBounds.set(0, 0, 5, 5); + //backdropBounds is (1, 1, 4, 4), content clip is (0, 0, 3, 3), content translate is (1, 1) + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorGREEN); + ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) { + auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { + SkPaint greenPaint; + greenPaint.setColor(SK_ColorGREEN); + greenPaint.setStyle(SkPaint::kFill_Style); + bottomHalfGreenCanvas.drawRect(0, 1, 2, 2, greenPaint); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(halfGreenNode); + android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(2, 2); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { + auto redNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeXYWH(0, 1, 2, 1); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(2, 2); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderLayer) { + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1); + surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE); + redNode->setLayerSurface(surfaceLayer1); + + //create a 2nd 2x2 layer and add it to the queue as well. + //make the layer's dirty area one half of the layer and verify only the dirty half is updated. + auto blueNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { + blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + }); + auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2); + surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE); + blueNode->setLayerSurface(surfaceLayer2); + + //attach both layers to the update queue + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + layerUpdateQueue.enqueueLayerWithDamage(redNode.get(), dirty); + layerUpdateQueue.enqueueLayerWithDamage(blueNode.get(), SkRect::MakeWH(2, 1)); + ASSERT_EQ(layerUpdateQueue.entries().size(), 2UL); + + bool opaque = true; + FrameBuilder::LightGeometry lightGeometry; + lightGeometry.radius = 1.0f; + lightGeometry.center = { 0.0f, 0.0f, 0.0f }; + BakedOpRenderer::LightInfo lightInfo; + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo); + ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE); + ASSERT_TRUE(layerUpdateQueue.entries().empty()); + redNode->setLayerSurface(sk_sp<SkSurface>()); + blueNode->setLayerSurface(sk_sp<SkSurface>()); +} diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp index 624d20763384..fa3e13dd71ba 100644 --- a/libs/hwui/utils/TestWindowContext.cpp +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -110,7 +110,7 @@ public: } bool capturePixels(SkBitmap* bmp) { - sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named); SkImageInfo destinationConfig = SkImageInfo::Make(mSize.width(), mSize.height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType, colorSpace); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4d59d574adc8..2adb26159622 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -415,6 +415,18 @@ android:launchMode="singleTop" android:excludeFromRecents="true" /> + <activity + android:name=".pip.phone.PipMenuActivity" + android:theme="@style/PipPhoneOverlayControlTheme" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:excludeFromRecents="true" + android:exported="false" + android:resizeableActivity="true" + android:supportsPictureInPicture="true" + android:stateNotNeeded="true" + android:taskAffinity="" + androidprv:alwaysFocusable="true" /> + <!-- platform logo easter egg activity --> <activity android:name=".DessertCase" diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml new file mode 100644 index 000000000000..88e6e725c976 --- /dev/null +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#33000000"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"> + <Button + android:id="@+id/expand_pip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:textSize="14sp" + android:textColor="#ffffffff" + android:text="@string/pip_phone_expand" + android:fontFamily="sans-serif" /> + </LinearLayout> +</FrameLayout> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d8c8b82eee4a..37a7e38eb4f9 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1675,6 +1675,9 @@ not appear on production builds ever. --> <string name="tuner_doze_always_on" translatable="false">Always on</string> + <!-- Making the PIP fullscreen --> + <string name="pip_phone_expand">Expand</string> + <!-- PIP section of the tuner. Non-translatable since it should not appear on production builds ever. --> <string name="picture_in_picture" translatable="false">Picture-in-Picture</string> @@ -1695,4 +1698,20 @@ not appear on production builds ever. --> <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string> + <!-- PIP tap once to break through to the activity. Non-translatable since it should + not appear on production builds ever. --> + <string name="pip_tap_through_title" translatable="false">Tap to interact</string> + + <!-- PIP tap once to break through to the activity. Non-translatable since it should + not appear on production builds ever. --> + <string name="pip_tap_through_summary" translatable="false">Tap once to interact with the activity</string> + + <!-- PIP snap to closest edge. Non-translatable since it should + not appear on production builds ever. --> + <string name="pip_snap_mode_edge_title" translatable="false">Snap to closest edge</string> + + <!-- PIP snap to closest edge. Non-translatable since it should + not appear on production builds ever. --> + <string name="pip_snap_mode_edge_summary" translatable="false">Snap to the closest edge</string> + </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index e5bc8b996cea..6661f076d848 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -56,6 +56,24 @@ <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item> </style> + <style name="PipPhoneOverlayControlTheme" parent="@android:style/Theme.Material"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowBackground">@drawable/forced_resizable_background</item> + <item name="android:colorBackgroundCacheHint">@null</item> + <item name="android:statusBarColor">@color/transparent</item> + <item name="android:windowAnimationStyle">@style/Animation.PipPhoneOverlayControl</item> + </style> + + <style name="Animation.PipPhoneOverlayControl" parent="@android:style/Animation"> + <item name="android:activityOpenEnterAnimation">@anim/forced_resizable_enter</item> + + <!-- If the target stack doesn't have focus, we do a task to front animation. --> + <item name="android:taskToFrontEnterAnimation">@anim/forced_resizable_enter</item> + <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item> + </style> + <style name="TextAppearance.StatusBar.HeadsUp" parent="@*android:style/TextAppearance.StatusBar"> </style> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 942f8472b7ef..f09d6e9fe052 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -137,6 +137,18 @@ android:summary="@string/pip_drag_to_dismiss_summary" sysui:defValue="true" /> + <com.android.systemui.tuner.TunerSwitch + android:key="pip_tap_through" + android:title="@string/pip_tap_through_title" + android:summary="@string/pip_tap_through_summary" + sysui:defValue="false" /> + + <com.android.systemui.tuner.TunerSwitch + android:key="pip_snap_mode_edge" + android:title="@string/pip_snap_mode_edge_title" + android:summary="@string/pip_snap_mode_edge_summary" + sysui:defValue="false" /> + </PreferenceScreen> <PreferenceScreen diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index f9a4f7ced6cc..7b8d27eef8b4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -34,6 +34,7 @@ public class PipManager { private IActivityManager mActivityManager; private IWindowManager mWindowManager; + private PipMenuActivityController mMenuController; private PipTouchHandler mTouchHandler; private PipManager() {} @@ -46,7 +47,9 @@ public class PipManager { mActivityManager = ActivityManagerNative.getDefault(); mWindowManager = WindowManagerGlobal.getWindowManagerService(); - mTouchHandler = new PipTouchHandler(context, mActivityManager, mWindowManager); + mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager); + mTouchHandler = new PipTouchHandler(context, mMenuController, mActivityManager, + mWindowManager); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java new file mode 100644 index 000000000000..bfe5cff90a24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 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.systemui.pip.phone; + +import android.annotation.Nullable; +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import com.android.systemui.R; + +/** + * Translucent activity that gets started on top of a task in PIP to allow the user to control it. + */ +public class PipMenuActivity extends Activity { + + private static final String TAG = "PipMenuActivity"; + + public static final int MESSAGE_FINISH_SELF = 2; + + private static final long INITIAL_DISMISS_DELAY = 2000; + private static final long POST_INTERACTION_DISMISS_DELAY = 1500; + + private Messenger mToControllerMessenger; + private Messenger mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_FINISH_SELF: + finish(); + break; + } + } + }); + + private final Runnable mFinishRunnable = new Runnable() { + @Override + public void run() { + finish(); + } + }; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent startingIntet = getIntent(); + mToControllerMessenger = startingIntet.getParcelableExtra( + PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER); + + setContentView(R.layout.pip_menu_activity); + findViewById(R.id.expand_pip).setOnClickListener((view) -> { + finish(); + notifyExpandPip(); + }); + } + + @Override + protected void onStart() { + super.onStart(); + notifyActivityVisibility(true); + repostDelayedFinish(INITIAL_DISMISS_DELAY); + } + + @Override + public void onUserInteraction() { + repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); + } + + @Override + protected void onStop() { + super.onStop(); + finish(); + } + + @Override + public void finish() { + View v = getWindow().getDecorView(); + v.removeCallbacks(mFinishRunnable); + notifyActivityVisibility(false); + super.finish(); + overridePendingTransition(0, R.anim.forced_resizable_exit); + } + + @Override + public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { + // Do nothing + } + + private void notifyActivityVisibility(boolean visible) { + Message m = Message.obtain(); + m.what = PipMenuActivityController.MESSAGE_ACTIVITY_VISIBILITY_CHANGED; + m.arg1 = visible ? 1 : 0; + m.replyTo = visible ? mMessenger : null; + try { + mToControllerMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify controller of PIP menu visibility", e); + } + } + + private void notifyExpandPip() { + Message m = Message.obtain(); + m.what = PipMenuActivityController.MESSAGE_EXPAND_PIP; + try { + mToControllerMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify controller to expand PIP", e); + } + } + + private void repostDelayedFinish(long delay) { + View v = getWindow().getDecorView(); + v.removeCallbacks(mFinishRunnable); + v.postDelayed(mFinishRunnable, delay); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java new file mode 100644 index 000000000000..d1bce0c74ccd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -0,0 +1,126 @@ +package com.android.systemui.pip.phone; + +import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; + +import android.app.ActivityManager.StackInfo; +import android.app.ActivityOptions; +import android.app.IActivityManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.view.IWindowManager; + +import java.util.ArrayList; + +public class PipMenuActivityController { + + private static final String TAG = "PipMenuActivityController"; + + public static final String EXTRA_CONTROLLER_MESSENGER = "messenger"; + public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 1; + public static final int MESSAGE_EXPAND_PIP = 3; + + /** + * A listener interface to receive notification on changes in PIP. + */ + public interface Listener { + /** + * Called when the PIP menu visibility changes. + */ + void onPipMenuVisibilityChanged(boolean visible); + } + + private Context mContext; + private IActivityManager mActivityManager; + private IWindowManager mWindowManager; + private ArrayList<Listener> mListeners = new ArrayList<>(); + + private Messenger mToActivityMessenger; + private Messenger mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_ACTIVITY_VISIBILITY_CHANGED: { + boolean visible = msg.arg1 > 0; + int listenerCount = mListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mListeners.get(i).onPipMenuVisibilityChanged(visible); + } + mToActivityMessenger = msg.replyTo; + break; + } + case MESSAGE_EXPAND_PIP: { + try { + mActivityManager.resizeStack(PINNED_STACK_ID, null, true, true, true, 225); + } catch (RemoteException e) { + Log.e(TAG, "Error showing PIP menu activity", e); + } + break; + } + } + } + }); + + public PipMenuActivityController(Context context, IActivityManager activityManager, + IWindowManager windowManager) { + mContext = context; + mActivityManager = activityManager; + mWindowManager = windowManager; + } + + /** + * Adds a new menu activity listener. + */ + public void addListener(Listener listener) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + + /** + * Shows the menu activity. + */ + public void showMenu() { + // Start the menu activity on the top task of the pinned stack + try { + StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && + pinnedStackInfo.taskIds.length > 0) { + Intent intent = new Intent(mContext, PipMenuActivity.class); + intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskId( + pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); + options.setTaskOverlay(true); + mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); + } else { + Log.e(TAG, "No PIP tasks found"); + } + } catch (RemoteException e) { + Log.e(TAG, "Error showing PIP menu activity", e); + } + } + + /** + * Hides the menu activity. + */ + public void hideMenu() { + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_FINISH_SELF; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify menu activity to finish", e); + } + mToActivityMessenger = null; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index e32022abdf38..a3593806fb1f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -32,6 +32,7 @@ import android.app.IActivityManager; import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.util.Log; @@ -61,6 +62,8 @@ public class PipTouchHandler implements TunerService.Tunable { private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss"; private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss"; + private static final String TUNER_KEY_TAP_THROUGH = "pip_tap_through"; + private static final String TUNER_KEY_SNAP_MODE_EDGE = "pip_snap_mode_edge"; private static final int SNAP_STACK_DURATION = 225; private static final int DISMISS_STACK_DURATION = 375; @@ -70,17 +73,20 @@ public class PipTouchHandler implements TunerService.Tunable { private final IActivityManager mActivityManager; private final IWindowManager mWindowManager; private final ViewConfiguration mViewConfig; - private final InputChannel mInputChannel = new InputChannel(); private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); + private final PipMenuListener mMenuListener = new PipMenuListener(); private IPinnedStackController mPinnedStackController; - private final PipInputEventReceiver mInputEventReceiver; + private PipInputEventReceiver mInputEventReceiver; + private PipMenuActivityController mMenuController; private PipDismissViewController mDismissViewController; private final PipSnapAlgorithm mSnapAlgorithm; private PipMotionHelper mMotionHelper; private boolean mEnableSwipeToDismiss = true; private boolean mEnableDragToDismiss = true; + private boolean mEnableTapThrough = false; + private boolean mEnableSnapToEdge = false; private final Rect mPinnedStackBounds = new Rect(); private final Rect mBoundedPinnedStackBounds = new Rect(); @@ -97,6 +103,7 @@ public class PipTouchHandler implements TunerService.Tunable { private final PointF mLastTouch = new PointF(); private boolean mIsDragging; private boolean mIsSwipingToDismiss; + private boolean mIsTappingThrough; private int mActivePointerId; private final FlingAnimationUtils mFlingAnimationUtils; @@ -120,7 +127,7 @@ public class PipTouchHandler implements TunerService.Tunable { // To be implemented for input handling over Pip windows if (event instanceof MotionEvent) { MotionEvent ev = (MotionEvent) event; - handleTouchEvent(ev); + handled = handleTouchEvent(ev); } } finally { finishInputEvent(event, handled); @@ -144,13 +151,26 @@ public class PipTouchHandler implements TunerService.Tunable { } } - public PipTouchHandler(Context context, IActivityManager activityManager, - IWindowManager windowManager) { + /** + * A listener for the PIP menu activity. + */ + private class PipMenuListener implements PipMenuActivityController.Listener { + @Override + public void onPipMenuVisibilityChanged(boolean visible) { + if (!visible) { + mIsTappingThrough = false; + registerInputConsumer(); + } else { + unregisterInputConsumer(); + } + } + } + + public PipTouchHandler(Context context, PipMenuActivityController menuController, + IActivityManager activityManager, IWindowManager windowManager) { // Initialize the Pip input consumer try { - windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel); windowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener); } catch (RemoteException e) { Log.e(TAG, "Failed to create PIP input consumer", e); @@ -159,22 +179,29 @@ public class PipTouchHandler implements TunerService.Tunable { mActivityManager = activityManager; mWindowManager = windowManager; mViewConfig = ViewConfiguration.get(context); - mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper()); - if (mEnableDragToDismiss) { - mDismissViewController = new PipDismissViewController(context); - } + mMenuController = menuController; + mMenuController.addListener(mMenuListener); + mDismissViewController = new PipDismissViewController(context); mSnapAlgorithm = new PipSnapAlgorithm(mContext); mFlingAnimationUtils = new FlingAnimationUtils(context, 2f); mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler()); + registerInputConsumer(); // Register any tuner settings changes TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS, - TUNER_KEY_DRAG_TO_DISMISS); + TUNER_KEY_DRAG_TO_DISMISS, TUNER_KEY_TAP_THROUGH, TUNER_KEY_SNAP_MODE_EDGE); } @Override public void onTuningChanged(String key, String newValue) { if (newValue == null) { + // Reset back to default + mEnableSwipeToDismiss = true; + mEnableDragToDismiss = true; + mEnableTapThrough = false; + mIsTappingThrough = false; + mEnableSnapToEdge = false; + setSnapToEdge(false); return; } switch (key) { @@ -184,6 +211,14 @@ public class PipTouchHandler implements TunerService.Tunable { case TUNER_KEY_DRAG_TO_DISMISS: mEnableDragToDismiss = Integer.parseInt(newValue) != 0; break; + case TUNER_KEY_TAP_THROUGH: + mEnableTapThrough = Integer.parseInt(newValue) != 0; + mIsTappingThrough = false; + break; + case TUNER_KEY_SNAP_MODE_EDGE: + mEnableSnapToEdge = Integer.parseInt(newValue) != 0; + setSnapToEdge(mEnableSnapToEdge); + break; } } @@ -192,10 +227,10 @@ public class PipTouchHandler implements TunerService.Tunable { updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */); } - private void handleTouchEvent(MotionEvent ev) { + private boolean handleTouchEvent(MotionEvent ev) { // Skip touch handling until we are bound to the controller if (mPinnedStackController == null) { - return; + return true; } switch (ev.getAction()) { @@ -239,6 +274,8 @@ public class PipTouchHandler implements TunerService.Tunable { float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y); if (movement > mViewConfig.getScaledTouchSlop()) { mIsDragging = true; + mIsTappingThrough = false; + mMenuController.hideMenu(); if (mEnableSwipeToDismiss) { // TODO: this check can have some buffer so that we only start swiping // after a significant move out of bounds @@ -328,7 +365,14 @@ public class PipTouchHandler implements TunerService.Tunable { } } } else { - expandPinnedStackToFullscreen(); + if (mEnableTapThrough) { + if (!mIsTappingThrough) { + mMenuController.showMenu(); + mIsTappingThrough = true; + } + } else { + expandPinnedStackToFullscreen(); + } } if (mEnableDragToDismiss) { mDismissViewController.destroyDismissTarget(); @@ -348,6 +392,7 @@ public class PipTouchHandler implements TunerService.Tunable { break; } } + return !mIsTappingThrough; } private void initOrResetVelocityTracker() { @@ -366,6 +411,44 @@ public class PipTouchHandler implements TunerService.Tunable { } /** + * Registers the input consumer. + */ + private void registerInputConsumer() { + final InputChannel inputChannel = new InputChannel(); + try { + mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel); + } catch (RemoteException e) { + Log.e(TAG, "Failed to create PIP input consumer", e); + } + mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); + } + + /** + * Unregisters the input consumer. + */ + private void unregisterInputConsumer() { + try { + mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + } catch (RemoteException e) { + Log.e(TAG, "Failed to destroy PIP input consumer", e); + } + mInputEventReceiver.dispose(); + } + + /** + * Sets the snap-to-edge state. + */ + private void setSnapToEdge(boolean snapToEdge) { + mSnapAlgorithm.setSnapToEdge(snapToEdge); + try { + mPinnedStackController.setSnapToEdge(snapToEdge); + } catch (RemoteException e) { + Log.e(TAG, "Could not set snap mode to edge", e); + } + } + + /** * Flings the PIP to the closest snap target. */ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) { diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 0cad814fe8b1..a2207b259123 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -585,6 +585,20 @@ public class LockSettingsService extends ILockSettings.Stub { Slog.e(TAG, "Unable to remove tied profile key", e); } } + + boolean isWatch = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WATCH); + // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts + // and device management the lockscreen must be re-enabled now for users that upgrade. + if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) { + final int userCount = users.size(); + for (int i = 0; i < userCount; i++) { + int id = users.get(i).id; + setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); + } + setString("migrated_wear_lockscreen_disabled", "true", 0); + Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices"); + } } catch (RemoteException re) { Slog.e(TAG, "Unable to migrate old data", re); } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index faca8db3ab87..1ccf7229cc87 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -99,6 +99,13 @@ class PinnedStackController { mInInteractiveMode = inInteractiveMode; }); } + + @Override + public void setSnapToEdge(final boolean snapToEdge) { + mHandler.post(() -> { + mSnapAlgorithm.setSnapToEdge(snapToEdge); + }); + } } /** diff --git a/services/tests/runtests.py b/services/tests/runtests.py index 35fec90f6ac5..7980dc23e8e2 100755 --- a/services/tests/runtests.py +++ b/services/tests/runtests.py @@ -61,8 +61,8 @@ def main(): print 'Running tests...' if len(sys.argv) != 1: - run('adb shell am instrument -w "%s" %s' % - (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:]))) + run('adb shell am instrument -w %s "%s"' % + (' '.join(sys.argv[1:]), INSTRUMENTED_PACKAGE_RUNNER)) return 0 # It would be nice if the activity manager accepted a list of packages, but diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 197884dcf85c..1efd2ed6446c 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -194,14 +194,14 @@ include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libaapt2_jni LOCAL_MODULE_CLASS := SHARED_LIBRARIES -LOCAL_MODULE_HOST_OS := darwin linux +LOCAL_MODULE_HOST_OS := darwin linux windows LOCAL_CFLAGS := $(cFlags) LOCAL_CFLAGS_darwin := $(cFlags_darwin) LOCAL_CFLAGS_windows := $(cFlags_windows) LOCAL_CPPFLAGS := $(cppFlags) LOCAL_C_INCLUDES := $(protoIncludes) LOCAL_SRC_FILES := $(toolSources) $(sourcesJni) -LOCAL_STATIC_LIBRARIES := libaapt2 libnativehelper $(hostStaticLibs) +LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs) LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) LOCAL_LDLIBS := $(hostLdLibs) LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) diff --git a/tools/aapt2/jni/Aapt2.java b/tools/aapt2/jni/Aapt2.java deleted file mode 100644 index aed23de92fba..000000000000 --- a/tools/aapt2/jni/Aapt2.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.android.tools.aapt2; - -import java.util.List; - -/** - * {@code aapt2} JNI interface. To use the {@code aapt2} native interface, the - * shared library must first be loaded and then a new instance of this class can - * be used to access the library. - */ -public class Aapt2 { - - /** - * Invokes {@code aapt2} to perform resource compilation. - * - * @param arguments arguments for compilation (see {@code Compile.cpp}) - */ - public static void compile(List<String> arguments) { - nativeCompile(arguments); - } - - /** - * Invokes {@code aapt2} to perform linking. - * - * @param arguments arguments for linking (see {@code Link.cpp}) - */ - public static void link(List<String> arguments) { - nativeLink(arguments); - } - - /** - * JNI call. - * - * @param arguments arguments for compilation (see {@code Compile.cpp}) - */ - private static native void nativeCompile(List<String> arguments); - - /** - * JNI call. - * - * @param arguments arguments for linking (see {@code Link.cpp}) - */ - private static native void nativeLink(List<String> arguments); -} - diff --git a/tools/aapt2/jni/Makefile b/tools/aapt2/jni/Makefile deleted file mode 100644 index a9e628f45ce5..000000000000 --- a/tools/aapt2/jni/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -# -# This Makefile will generate the JNI headers for the Aapt2 class. -# - -AAPT2_PKG=com.android.tools.aapt2 -AAPT2_DIR=$(shell echo -n com/android/tools/aapt2 | tr . /) -OUT=out -OUT_CLASSES=$(OUT)/classes -OUT_HEADERS=. - -AAPT2_JAVA=Aapt2.java -AAPT2_CLASSES=$(OUT_CLASSES)/$(AAPT2_DIR)/Aapt2.class - -AAPT2_HEADERS=$(OUT_HEADERS)/Aapt2.h - -all: $(AAPT2_HEADERS) - -$(AAPT2_HEADERS): $(AAPT2_JAVA) $(AAPT2_CLASSES) - mkdir -p $(OUT_HEADERS) - $(JAVA_HOME)/bin/javah -d $(OUT_HEADERS) -cp $(OUT_CLASSES) $(AAPT2_PKG).Aapt2 - -$(AAPT2_CLASSES): $(AAPT2_JAVA) - mkdir -p $(OUT_CLASSES) - javac -d $(OUT_CLASSES) $(AAPT2_JAVA) - diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h new file mode 100644 index 000000000000..a8c4b136dcf6 --- /dev/null +++ b/tools/aapt2/jni/ScopedUtfChars.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef SCOPED_UTF_CHARS_H_included +#define SCOPED_UTF_CHARS_H_included + +#include <string.h> +#include <jni.h> + +#include "android-base/logging.h" + +// This file was copied with some minor modifications from libnativehelper. +// As soon as libnativehelper can be compiled for Windows, this file should be +// replaced with libnativehelper's implementation. +class ScopedUtfChars { + public: + ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { + CHECK(s != nullptr); + utf_chars_ = env->GetStringUTFChars(s, nullptr); + } + + ScopedUtfChars(ScopedUtfChars&& rhs) : + env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) { + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + + ~ScopedUtfChars() { + if (utf_chars_) { + env_->ReleaseStringUTFChars(string_, utf_chars_); + } + } + + ScopedUtfChars& operator=(ScopedUtfChars&& rhs) { + if (this != &rhs) { + // Delete the currently owned UTF chars. + this->~ScopedUtfChars(); + + // Move the rhs ScopedUtfChars and zero it out. + env_ = rhs.env_; + string_ = rhs.string_; + utf_chars_ = rhs.utf_chars_; + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + return *this; + } + + const char* c_str() const { + return utf_chars_; + } + + size_t size() const { + return strlen(utf_chars_); + } + + const char& operator[](size_t n) const { + return utf_chars_[n]; + } + + private: + JNIEnv* env_; + jstring string_; + const char* utf_chars_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); +}; + +#endif // SCOPED_UTF_CHARS_H_included diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp index 211ada8db078..24e83609c35a 100644 --- a/tools/aapt2/jni/aapt2_jni.cpp +++ b/tools/aapt2/jni/aapt2_jni.cpp @@ -22,7 +22,7 @@ #include <vector> #include "android-base/logging.h" -#include "nativehelper/ScopedUtfChars.h" +#include "ScopedUtfChars.h" #include "util/Util.h" @@ -46,8 +46,8 @@ static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) { // Now, iterate all strings in the list // (note: generic erasure means get() return an Object) - jmethodID get_method_id = - env->GetMethodID(list_cls, "get", "()Ljava/lang/Object;"); + jmethodID get_method_id = env->GetMethodID(list_cls, "get", "(I)Ljava/lang/Object;"); + CHECK(get_method_id != 0); for (jint i = 0; i < size; i++) { // Call get(i) to get the string in the ith position. jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i); @@ -92,3 +92,8 @@ JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_nativeLink( std::vector<aapt::StringPiece> link_args = extract_pieces(link_args_jni); aapt::Link(link_args); } + +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_ping( + JNIEnv *env, jclass aapt_obj) { + // This is just a dummy method to see if the library has been loaded. +} diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h index 443b98f2590d..26dc52db8695 100644 --- a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h +++ b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2.h @@ -9,6 +9,14 @@ extern "C" { #endif /* * Class: com_android_tools_aapt2_Aapt2 + * Method: ping + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2_ping + (JNIEnv *, jclass); + +/* + * Class: com_android_tools_aapt2_Aapt2 * Method: nativeCompile * Signature: (Ljava/util/List;)V */ |