Merge "Bring back the sensing alarm. We need it!"
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index c1eb80d..37d6757 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -343,15 +343,15 @@
int seconds = (int) Math.floor(duration / 1000);
int days = 0, hours = 0, minutes = 0;
- if (seconds > SECONDS_PER_DAY) {
+ if (seconds >= SECONDS_PER_DAY) {
days = seconds / SECONDS_PER_DAY;
seconds -= days * SECONDS_PER_DAY;
}
- if (seconds > SECONDS_PER_HOUR) {
+ if (seconds >= SECONDS_PER_HOUR) {
hours = seconds / SECONDS_PER_HOUR;
seconds -= hours * SECONDS_PER_HOUR;
}
- if (seconds > SECONDS_PER_MINUTE) {
+ if (seconds >= SECONDS_PER_MINUTE) {
minutes = seconds / SECONDS_PER_MINUTE;
seconds -= minutes * SECONDS_PER_MINUTE;
}
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
index 74c8e04..2370627 100644
--- a/core/tests/coretests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java
@@ -436,15 +436,17 @@
assertFormatDuration("+100ms", 100);
assertFormatDuration("+101ms", 101);
assertFormatDuration("+330ms", 330);
+ assertFormatDuration("+1s0ms", 1000);
assertFormatDuration("+1s330ms", 1330);
assertFormatDuration("+10s24ms", 10024);
+ assertFormatDuration("+1m0s30ms", 60030);
+ assertFormatDuration("+1h0m0s30ms", 3600030);
+ assertFormatDuration("+1d0h0m0s30ms", 86400030);
}
public void testFormatHugeDuration() {
- //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
- // TODO: improve formatDuration() API
- assertFormatDuration("+999d23h59m59s999ms", 1342833071555L);
- assertFormatDuration("-999d23h59m59s999ms", -1342833071555L);
+ assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L);
+ assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L);
}
private void assertFormatDuration(String expected, long duration) {
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index 3bd17fa..abcccbd 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -99,7 +99,7 @@
/** @hide */
public void printShortString(PrintWriter pw) {
- pw.println("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
+ pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
}
/**
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 94806ca..7dbba25 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -25,6 +25,15 @@
namespace android {
namespace uirenderer {
+void BakedOpRenderer::Info::setViewport(uint32_t width, uint32_t height) {
+ viewportWidth = width;
+ viewportHeight = height;
+ orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
+
+ renderState.setViewport(width, height);
+ renderState.blend().syncEnabled();
+}
+
Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
if (!texture) {
@@ -45,9 +54,54 @@
didDraw = true;
}
-void BakedOpRenderer::startFrame(Info& info) {
- info.renderState.setViewport(info.viewportWidth, info.viewportHeight);
- info.renderState.blend().syncEnabled();
+Layer* BakedOpRenderer::startLayer(Info& info, uint32_t width, uint32_t height) {
+ info.caches.textureState().activateTexture(0);
+ Layer* layer = info.caches.layerCache.get(info.renderState, width, height);
+ LOG_ALWAYS_FATAL_IF(!layer, "need layer...");
+
+ info.layer = layer;
+ layer->texCoords.set(0.0f, width / float(layer->getHeight()),
+ height / float(layer->getWidth()), 0.0f);
+
+ layer->setFbo(info.renderState.genFramebuffer());
+ info.renderState.bindFramebuffer(layer->getFbo());
+ layer->bindTexture();
+
+ // Initialize the texture if needed
+ if (layer->isEmpty()) {
+ layer->allocateTexture();
+ layer->setEmpty(false);
+ }
+
+ // attach the texture to the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ layer->getTextureId(), 0);
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
+ LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
+ "framebuffer incomplete!");
+
+ // Clear the FBO
+ info.renderState.scissor().setEnabled(false);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Change the viewport & ortho projection
+ info.setViewport(width, height);
+ return layer;
+}
+
+void BakedOpRenderer::endLayer(Info& info) {
+ Layer* layer = info.layer;
+ info.layer = nullptr;
+
+ // Detach the texture from the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
+ layer->removeFbo(false);
+}
+
+void BakedOpRenderer::startFrame(Info& info, uint32_t width, uint32_t height) {
+ info.renderState.bindFramebuffer(0);
+ info.setViewport(width, height);
Caches::getInstance().clearGarbage();
if (!info.opaque) {
@@ -130,7 +184,31 @@
}
void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
+ Layer* layer = *op.layerHandle;
+
+ // TODO: make this work for HW layers
+ layer->setPaint(op.paint);
+ layer->setBlend(true);
+ float layerAlpha = (layer->getAlpha() / 255.0f) * state.alpha;
+
+ const bool tryToSnap = state.computedState.transform.isPureTranslate();
+ Glop glop;
+ GlopBuilder(info.renderState, info.caches, &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUvQuad(nullptr, layer->texCoords)
+ .setFillLayer(layer->getTexture(), layer->getColorFilter(), layerAlpha, layer->getMode(), Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+ .build();
+ info.renderGlop(state, glop);
+
+ // return layer to cache, since each clipped savelayer is only drawn once.
+ layer->setConvexMask(nullptr);
+ if (!info.caches.layerCache.put(layer)) {
+ // Failing to add the layer to the cache should happen only if the layer is too large
+ LAYER_LOGD("Deleting layer");
+ layer->decStrong(nullptr);
+ }
}
} // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index f45dbe4..616adde 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -25,21 +25,21 @@
class Caches;
struct Glop;
+class Layer;
class RenderState;
class BakedOpRenderer {
public:
class Info {
public:
- Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque)
+ Info(Caches& caches, RenderState& renderState, bool opaque)
: renderState(renderState)
, caches(caches)
- , opaque(opaque)
- , viewportWidth(viewportWidth)
- , viewportHeight(viewportHeight) {
- orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
+ , opaque(opaque) {
}
+ void setViewport(uint32_t width, uint32_t height);
+
Texture* getTexture(const SkBitmap* bitmap);
void renderGlop(const BakedOpState& state, const Glop& glop);
@@ -47,16 +47,19 @@
Caches& caches;
bool didDraw = false;
- bool opaque;
+ Layer* layer = nullptr;
// where should these live? layer state object?
- int viewportWidth;
- int viewportHeight;
+ bool opaque;
+ uint32_t viewportWidth = 0;
+ uint32_t viewportHeight = 0;
Matrix4 orthoMatrix;
};
- static void startFrame(Info& info);
+ static Layer* startLayer(Info& info, uint32_t width, uint32_t height);
+ static void endLayer(Info& info);
+ static void startFrame(Info& info, uint32_t width, uint32_t height);
static void endFrame(Info& info);
/**
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index f99d92b..489ebc1 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -155,8 +155,7 @@
if (fbo) {
if (flush) LayerRenderer::flushLayer(renderState, this);
- // If put fails the cache will delete the FBO
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
fbo = 0;
}
}
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 227271d..e9e5d81 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -189,7 +189,7 @@
LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
Caches& caches = Caches::getInstance();
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.genFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return nullptr;
@@ -204,7 +204,7 @@
// We first obtain a layer before comparing against the max texture size
// because layers are not allocated at the exact desired size. They are
- // always created slighly larger to improve recycling
+ // always created slightly larger to improve recycling
const uint32_t maxTextureSize = caches.maxTextureSize;
if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) {
ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -357,7 +357,7 @@
&& bitmap->width() <= caches.maxTextureSize
&& bitmap->height() <= caches.maxTextureSize) {
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.getFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return false;
@@ -465,7 +465,7 @@
layer->setAlpha(alpha, mode);
layer->setFbo(previousLayerFbo);
caches.textureState().deleteTexture(texture);
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
renderState.setViewport(previousViewportWidth, previousViewportHeight);
return status;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index c1417c4..cde42f8 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -203,7 +203,7 @@
};
// iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still interate to find similar batch to insert after
+// if no target, merging ops still iterate to find similar batch to insert after
void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
BatchBase** targetBatch, size_t* insertBatchIndex) const {
for (int i = mBatches.size() - 1; i >= 0; i--) {
@@ -292,18 +292,14 @@
}
}
-OpReorderer::OpReorderer()
+OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+ const std::vector< sp<RenderNode> >& nodes)
: mCanvasState(*this) {
- mLayerReorderers.emplace_back();
+ ATRACE_NAME("prepare drawing commands");
+
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
mLayerStack.push_back(0);
-}
-void OpReorderer::onViewportInitialized() {}
-
-void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-
-void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
- const std::vector< sp<RenderNode> >& nodes) {
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Vector3());
@@ -321,13 +317,22 @@
}
}
-void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) {
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+ : mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
+
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+ mLayerStack.push_back(0);
+
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
0, 0, viewportWidth, viewportHeight, Vector3());
deferImpl(displayList);
}
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
@@ -350,11 +355,6 @@
void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
ATRACE_NAME("flush drawing commands");
- // Relay through layers in reverse order, since layers
- // later in the list will be drawn by earlier ones
- for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
- mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
- }
}
void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
@@ -405,15 +405,17 @@
// TODO: test rejection at defer time, where the bounds become empty
void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+ const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+ const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
mCanvasState.writableSnapshot()->transform->loadIdentity();
- mCanvasState.writableSnapshot()->initializeViewport(
- (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+ mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
// create a new layer, and push its index on the stack
mLayerStack.push_back(mLayerReorderers.size());
- mLayerReorderers.emplace_back();
+ mLayerReorderers.emplace_back(layerWidth, layerHeight);
mLayerReorderers.back().beginLayerOp = &op;
}
@@ -432,7 +434,8 @@
beginLayerOp.unmappedBounds,
beginLayerOp.localMatrix,
beginLayerOp.localClipRect,
- beginLayerOp.paint);
+ beginLayerOp.paint,
+ &mLayerReorderers[finishedLayerIndex].layer);
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
if (bakedOpState) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 73dc9af..f32b858 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -63,6 +63,10 @@
*/
class LayerReorderer {
public:
+ LayerReorderer(uint32_t width, uint32_t height)
+ : width(width)
+ , height(height) {}
+
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
void locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -77,15 +81,22 @@
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+ bool empty() const {
+ return mBatches.empty();
+ }
+
void clear() {
mBatches.clear();
}
void dump() const;
+ Layer* layer = nullptr;
const BeginLayerOp* beginLayerOp = nullptr;
-
+ const uint32_t width;
+ const uint32_t height;
private:
+
std::vector<BatchBase*> mBatches;
/**
@@ -100,14 +111,13 @@
};
public:
- OpReorderer();
- virtual ~OpReorderer() {}
-
// TODO: not final, just presented this way for simplicity. Layers too?
- void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
+ OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes);
- void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+ OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+
+ virtual ~OpReorderer() {}
/**
* replayBakedOps() is templated based on what class will receive ops being replayed.
@@ -128,8 +138,21 @@
static BakedOpReceiver receivers[] = {
MAP_OPS(BAKED_OP_RECEIVER)
};
- StaticReceiver::startFrame(arg);
- replayBakedOpsImpl((void*)&arg, receivers);
+
+ // Relay through layers in reverse order, since layers
+ // later in the list will be drawn by earlier ones
+ for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
+ LayerReorderer& layer = mLayerReorderers[i];
+ if (!layer.empty()) {
+ layer.layer = StaticReceiver::startLayer(arg, layer.width, layer.height);
+ layer.replayBakedOpsImpl((void*)&arg, receivers);
+ StaticReceiver::endLayer(arg);
+ }
+ }
+
+ const LayerReorderer& fbo0 = mLayerReorderers[0];
+ StaticReceiver::startFrame(arg, fbo0.width, fbo0.height);
+ fbo0.replayBakedOpsImpl((void*)&arg, receivers);
StaticReceiver::endFrame(arg);
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d4f65b6..8c3603b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -673,7 +673,7 @@
bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
layer->clipRect.set(clip);
- layer->setFbo(mCaches.fboCache.get());
+ layer->setFbo(mRenderState.genFramebuffer());
writableSnapshot()->region = &writableSnapshot()->layer->region;
writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index dd01637..6c31b42 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -29,6 +29,7 @@
namespace android {
namespace uirenderer {
+class Layer;
class RenderNode;
struct Vertex;
@@ -136,8 +137,12 @@
};
struct LayerOp : RecordedOp {
- LayerOp(BASE_PARAMS)
- : SUPER(LayerOp) {}
+ LayerOp(BASE_PARAMS, Layer** layerHandle)
+ : SUPER(LayerOp)
+ , layerHandle(layerHandle) {}
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ Layer** layerHandle;
};
}; // namespace uirenderer
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index cf96d44..382c0bd 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,8 +48,7 @@
void BM_OpReorderer_defer::Run(int iters) {
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer;
- reorderer.defer(200, 200, *sReorderingDisplayList);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -60,11 +59,10 @@
TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer;
- reorderer.defer(200, 200, *sReorderingDisplayList);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList);
MicroBench::DoNotOptimize(&reorderer);
- BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
+ BakedOpRenderer::Info info(caches, renderState, true);
reorderer.replayBakedOps<BakedOpRenderer>(info);
}
StopBenchmarkTiming();
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index dfa70ac..9637117 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -125,6 +125,21 @@
}
}
+GLuint RenderState::genFramebuffer() {
+ GLuint ret;
+ glGenFramebuffers(1, &ret);
+ return ret;
+}
+
+void RenderState::deleteFramebuffer(GLuint fbo) {
+ if (mFramebuffer == fbo) {
+ // GL defines that deleting the currently bound FBO rebinds FBO 0.
+ // Reflect this in our cached value.
+ mFramebuffer = 0;
+ }
+ glDeleteFramebuffers(1, &fbo);
+}
+
void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
if (mode == DrawGlInfo::kModeProcessNoContext) {
// If there's no context we don't need to interrupt as there's
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9ae0845..87a7996 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -57,7 +57,10 @@
void getViewport(GLsizei* outWidth, GLsizei* outHeight);
void bindFramebuffer(GLuint fbo);
- GLint getFramebuffer() { return mFramebuffer; }
+ GLuint getFramebuffer() { return mFramebuffer; }
+ GLuint genFramebuffer();
+ void deleteFramebuffer(GLuint fbo);
+
void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f571426..7c0f0b6 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -318,10 +318,8 @@
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer;
- reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes);
- BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
- frame.width(), frame.height(), mOpaque);
+ OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+ BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
// TODO: profiler().draw(mCanvas);
reorderer.replayBakedOps<BakedOpRenderer>(info);
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index b353ae6..485759b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -333,7 +333,7 @@
if (CC_LIKELY(err == EGL_SUCCESS)) {
return true;
}
- if (err == EGL_BAD_SURFACE) {
+ if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) {
// For some reason our surface was destroyed out from under us
// This really shouldn't happen, but if it does we can recover easily
// by just not trying to use the surface anymore
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 891af91..2eefd37 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -54,15 +54,10 @@
}
};
-static TestCanvas* startRecording(RenderNode* node) {
- TestCanvas* renderer = new TestCanvas(
- node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
- return renderer;
-}
-
-static void endRecording(TestCanvas* renderer, RenderNode* node) {
- node->setStagingDisplayList(renderer->finishRecording());
- delete renderer;
+static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
+ TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
+ contentCallback(canvas);
+ node.setStagingDisplayList(canvas.finishRecording());
}
class TreeContentAnimation {
@@ -75,7 +70,7 @@
frameCount = fc;
}
}
- virtual void createContent(int width, int height, TestCanvas* renderer) = 0;
+ virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
virtual void doFrame(int frameNr) = 0;
template <class T>
@@ -108,11 +103,9 @@
proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
- android::uirenderer::Rect DUMMY;
-
- TestCanvas* renderer = startRecording(rootNode);
- animation.createContent(width, height, renderer);
- endRecording(renderer, rootNode);
+ recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
+ animation.createContent(width, height, &canvas); //TODO: no&
+ });
// Do a few cold runs then reset the stats so that the caches are all hot
for (int i = 0; i < 3; i++) {
@@ -140,19 +133,19 @@
class ShadowGridAnimation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -171,9 +164,9 @@
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -187,19 +180,19 @@
class ShadowGrid2Animation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -218,9 +211,9 @@
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -233,15 +226,30 @@
class RectGridAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
- card = createCard(40, 40, 200, 200);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(false);
+ SkRegion region;
+ for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+ for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+ region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+ }
+ }
+
+ SkPaint paint;
+ paint.setColor(0xff00ffff);
+ canvas.drawRegion(region, paint);
+ });
+ canvas->drawRenderNode(card.get());
+
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -249,29 +257,6 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
- SkRegion region;
- for (int xOffset = 0; xOffset < width; xOffset+=2) {
- for (int yOffset = 0; yOffset < height; yOffset+=2) {
- region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
- }
- }
-
- SkPaint paint;
- paint.setColor(0xff00ffff);
- renderer->drawRegion(region, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
static Benchmark _RectGrid(BenchmarkInfo{
"rectgrid",
@@ -282,15 +267,22 @@
class OvalAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->insertReorderBarrier(true);
- card = createCard(40, 40, 400, 400);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(0xFF000000);
+ canvas.drawOval(0, 0, 200, 200, paint);
+ });
+ canvas->drawRenderNode(card.get());
- renderer->insertReorderBarrier(false);
+ canvas->insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
@@ -299,22 +291,6 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
-
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setColor(0xFF000000);
- renderer->drawOval(0, 0, width, height, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
static Benchmark _Oval(BenchmarkInfo{
"oval",
@@ -325,7 +301,7 @@
class PartialDamageTest : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* renderer) override {
+ void createContent(int width, int height, TestCanvas* canvas) override {
static SkColor COLORS[] = {
0xFFF44336,
0xFF9C27B0,
@@ -333,13 +309,13 @@
0xFF4CAF50,
};
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
COLORS[static_cast<int>((y / dp(116))) % 4]);
- renderer->drawRenderNode(card.get());
+ canvas->drawRenderNode(card.get());
cards.push_back(card);
}
}
@@ -350,10 +326,10 @@
cards[0]->mutateStagingProperties().setTranslationY(curFrame);
cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- TestCanvas* renderer = startRecording(cards[0].get());
- renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
- SkXfermode::kSrcOver_Mode);
- endRecording(renderer, cards[0].get());
+ recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+ canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
+ SkXfermode::kSrcOver_Mode);
+ });
}
static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
@@ -378,9 +354,9 @@
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- TestCanvas* renderer = startRecording(node.get());
- renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
+ recordNode(*node, [color](TestCanvas& canvas) {
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
return node;
}
};
@@ -393,16 +369,24 @@
});
-class SimpleRectGridAnimation : public TreeContentAnimation {
+class SaveLayerAnimation : public TreeContentAnimation {
public:
- sp<RenderNode> card;
- void createContent(int width, int height, TestCanvas* renderer) override {
- SkPaint paint;
- paint.setColor(0xFF00FFFF);
- renderer->drawRect(0, 0, width, height, paint);
+ sp<RenderNode> card = new RenderNode();
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
- card = createCard(40, 40, 200, 200);
- renderer->drawRenderNode(card.get());
+ card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ recordNode(*card, [](TestCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+ canvas.restore();
+ canvas.restore();
+ });
+
+ canvas->drawRenderNode(card.get());
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
@@ -410,24 +394,10 @@
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- TestCanvas* renderer = startRecording(node.get());
- SkPaint paint;
- paint.setColor(0xFFFF00FF);
- renderer->drawRect(0, 0, width, height, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
};
-static Benchmark _SimpleRectGrid(BenchmarkInfo{
- "simplerectgrid",
- "A simple collection of rects. "
- "Low CPU/GPU load.",
- TreeContentAnimation::run<SimpleRectGridAnimation>
+static Benchmark _SaveLayer(BenchmarkInfo{
+ "savelayer",
+ "A nested pair of clipped saveLayer operations. "
+ "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+ TreeContentAnimation::run<SaveLayerAnimation>
});
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 82aebea..bc1b69f 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -85,13 +85,5 @@
}
}
-#define UNSUPPORTED_OP(Info, Type) \
- static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
-
-class Info {
-public:
- int index = 0;
-};
-
}
}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index d02f89d..af9c3f2 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -35,19 +35,22 @@
* for every test.
*
* onXXXOp methods fail by default - tests should override ops they expect
+ * startLayer fails by default - tests should override if expected
* startFrame/endFrame do nothing by default - tests should override to intercept
*/
template<class CustomClient, class Arg>
class TestReceiver {
public:
#define CLIENT_METHOD(Type) \
- virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
+ virtual void on##Type(Arg&, const Type&, const BakedOpState&) { ADD_FAILURE(); }
class Client {
public:
virtual ~Client() {};
MAP_OPS(CLIENT_METHOD)
- virtual void startFrame(Arg& info) {}
+ virtual Layer* startLayer(Arg& info, uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
+ virtual void endLayer(Arg& info) { ADD_FAILURE(); }
+ virtual void startFrame(Arg& info, uint32_t width, uint32_t height) {}
virtual void endFrame(Arg& info) {}
};
@@ -57,11 +60,18 @@
}
MAP_OPS(DISPATCHER_METHOD)
- static void startFrame(Arg& info) {
+ static Layer* startLayer(Arg& info, uint32_t width, uint32_t height) {
CustomClient client;
- client.startFrame(info);
+ return client.startLayer(info, width, height);
}
-
+ static void endLayer(Arg& info) {
+ CustomClient client;
+ client.endLayer(info);
+ }
+ static void startFrame(Arg& info, uint32_t width, uint32_t height) {
+ CustomClient client;
+ client.startFrame(info, width, height);
+ }
static void endFrame(Arg& info) {
CustomClient client;
client.endFrame(info);
@@ -78,8 +88,10 @@
class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
public:
- void startFrame(Info& info) override {
+ void startFrame(Info& info, uint32_t width, uint32_t height) override {
EXPECT_EQ(0, info.index++);
+ EXPECT_EQ(100u, width);
+ EXPECT_EQ(200u, height);
}
void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
EXPECT_EQ(1, info.index++);
@@ -97,8 +109,7 @@
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(100, 200, *dl);
Info info;
reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
@@ -113,8 +124,7 @@
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
Info info;
reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
@@ -146,8 +156,7 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
Info info;
reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
@@ -167,7 +176,7 @@
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
break;
default:
- FAIL();
+ ADD_FAILURE();
}
}
};
@@ -196,8 +205,7 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
- OpReorderer reorderer;
- reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
Info info;
reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
@@ -221,8 +229,7 @@
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
- OpReorderer reorderer;
- reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+ OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
200, 200, nodes);
Info info;
@@ -232,8 +239,17 @@
class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
public:
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ Layer* startLayer(Info& info, uint32_t width, uint32_t height) override {
EXPECT_EQ(0, info.index++);
+ EXPECT_EQ(180u, width);
+ EXPECT_EQ(180u, height);
+ return nullptr;
+ }
+ void endLayer(Info& info) override {
+ EXPECT_EQ(2, info.index++);
+ }
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, info.index++);
EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
@@ -243,7 +259,7 @@
EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
}
void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, info.index++);
+ EXPECT_EQ(3, info.index++);
EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
EXPECT_TRUE(state.computedState.transform.isIdentity());
@@ -256,33 +272,61 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
Info info;
reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
- EXPECT_EQ(2, info.index);
+ EXPECT_EQ(4, info.index);
}
-// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
+/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
+ * - startLayer2, rect2 endLayer2
+ * - startLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
public:
- void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ Layer* startLayer(Info& info, uint32_t width, uint32_t height) override {
const int index = info.index++;
if (index == 0) {
+ EXPECT_EQ(400u, width);
+ EXPECT_EQ(400u, height);
+ return (Layer*) 0x400;
+ } else if (index == 3) {
+ EXPECT_EQ(800u, width);
+ EXPECT_EQ(800u, height);
+ return (Layer*) 0x800;
+ } else { ADD_FAILURE(); }
+ return (Layer*) nullptr;
+ }
+ void endLayer(Info& info) override {
+ int index = info.index++;
+ EXPECT_TRUE(index == 2 || index == 6);
+ }
+ void startFrame(Info& info, uint32_t width, uint32_t height) override {
+ EXPECT_EQ(7, info.index++);
+ }
+ void endFrame(Info& info) override {
+ EXPECT_EQ(9, info.index++);
+ }
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ const int index = info.index++;
+ if (index == 1) {
EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
- } else if (index == 1) {
+ } else if (index == 4) {
EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
- } else { FAIL(); }
+ } else { ADD_FAILURE(); }
}
void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
const int index = info.index++;
- if (index == 2) {
+ if (index == 5) {
+ EXPECT_EQ((Layer*)0x400, *op.layerHandle);
EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
- } else if (index == 3) {
+ } else if (index == 8) {
+ EXPECT_EQ((Layer*)0x800, *op.layerHandle);
EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
- } else { FAIL(); }
+ } else { ADD_FAILURE(); }
}
};
TEST(OpReorderer, saveLayerNested) {
@@ -299,12 +343,11 @@
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(800, 800, *dl);
+ OpReorderer reorderer(800, 800, *dl);
Info info;
reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
- EXPECT_EQ(4, info.index);
+ EXPECT_EQ(10, info.index);
}
TEST(OpReorderer, saveLayerContentRejection) {
@@ -319,8 +362,7 @@
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer;
- reorderer.defer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl);
Info info;
// should see no ops, even within the layer, since the layer should be rejected
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c023123..e8cdf46 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -38,7 +38,7 @@
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.restore();
});
- playbackOps(*dl, [](const RecordedOp& op) { FAIL(); });
+ playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
TEST(RecordingCanvas, testSimpleRectRecord) {
@@ -135,7 +135,7 @@
// TODO: add asserts
break;
default:
- FAIL();
+ ADD_FAILURE();
}
});
EXPECT_EQ(3, count);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 98cfc7c..7e47006 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2876,13 +2876,16 @@
}
if (stackId != task.stack.mStackId) {
- moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason);
- } else {
- task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
- task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker,
- reason);
+ moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+
+ // moveTaskToStackUncheckedLocked() should already placed the task on top,
+ // still need moveTaskToFrontLocked() below for any transition settings.
}
+ final ActivityRecord r = task.getTopActivity();
+ task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
+ r == null ? null : r.appTimeTracker, reason);
+
if (DEBUG_STACK) Slog.d(TAG_STACK,
"findTaskToMoveToFront: moved to front of stack=" + task.stack);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 2365a45..dc34904 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -456,10 +456,12 @@
return -startPos / denom;
}
- private Animation createScaleUpAnimationLocked(
- int transit, boolean enter, int appWidth, int appHeight) {
- Animation a = null;
+ private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+ Rect containingFrame) {
+ Animation a;
getDefaultNextAppTransitionStartRect(mTmpStartRect);
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
if (enter) {
// Entering app zooms out from the center of the initial rect.
float scaleW = mTmpStartRect.width() / (float) appWidth;
@@ -746,10 +748,11 @@
* activity that is leaving, and the activity that is entering.
*/
Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
- int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
- Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
- int taskId) {
+ int orientation, int transit, Rect containingFrame, Rect contentInsets,
+ @Nullable Rect surfaceInsets, boolean freeform, int taskId) {
Animation a;
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
getDefaultNextAppTransitionStartRect(mTmpStartRect);
final int thumbWidthI = mTmpStartRect.width();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -762,7 +765,7 @@
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
- if (resizedWindow) {
+ if (freeform) {
a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
containingFrame, surfaceInsets, taskId);
} else {
@@ -953,8 +956,10 @@
* This animation is created when we are doing a thumbnail transition, for the activity that is
* leaving, and the activity that is entering.
*/
- Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
- int appHeight, int transit, int taskId) {
+ Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
+ int transit, int taskId) {
+ final int appWidth = containingFrame.width();
+ final int appHeight = containingFrame.height();
Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
Animation a;
getDefaultNextAppTransitionStartRect(mTmpStartRect);
@@ -1016,18 +1021,22 @@
return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
}
- private Animation createRelaunchAnimation(int appWidth, int appHeight,
- Rect containingFrame) {
+ private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
final int left = mTmpFromClipRect.left;
final int top = mTmpFromClipRect.top;
mTmpFromClipRect.offset(-left, -top);
- mTmpToClipRect.set(0, 0, appWidth, appHeight);
+ // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+ mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
AnimationSet set = new AnimationSet(true);
float fromWidth = mTmpFromClipRect.width();
float toWidth = mTmpToClipRect.width();
float fromHeight = mTmpFromClipRect.height();
- float toHeight = mTmpToClipRect.height();
+ // While the window might span the whole display, the actual content will be cropped to the
+ // system decoration frame, for example when the window is docked. We need to take into
+ // account the visible height when constructing the animation.
+ float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
+ int translateAdjustment = 0;
if (fromWidth <= toWidth && fromHeight <= toHeight) {
// The final window is larger in both dimensions than current window (e.g. we are
// maximizing), so we can simply unclip the new window and there will be no disappearing
@@ -1037,12 +1046,17 @@
// The disappearing window has one larger dimension. We need to apply scaling, so the
// first frame of the entry animation matches the old window.
set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+ // We might not be going exactly full screen, but instead be aligned under the status
+ // bar using cropping. We still need to account for the cropped part, which will also
+ // be scaled.
+ translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
}
- // We might not be going exactly full screen, but instead be aligned under the status bar.
- // We need to take this into account when creating the translate animation.
+ // We animate the translation from the old position of the removed window, to the new
+ // position of the added window. The latter might not be full screen, for example docked for
+ // docked windows.
TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
- 0, top - containingFrame.top, 0);
+ 0, top - containingFrame.top - translateAdjustment, 0);
set.addAnimation(translate);
set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
set.setZAdjustment(Animation.ZORDER_TOP);
@@ -1060,10 +1074,30 @@
&& mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL;
}
+ /**
+ *
+ * @param frame These are the bounds of the window when it finishes the animation. This is where
+ * the animation must usually finish in entrance animation, as the next frame will
+ * display the window at these coordinates. In case of exit animation, this is
+ * where the animation must start, as the frame before the animation is displaying
+ * the window at these bounds.
+ * @param insets Knowing where the window will be positioned is not enough. Some parts of the
+ * window might be obscured, usually by the system windows (status bar and
+ * navigation bar) and we use content insets to convey that information. This
+ * usually affects the animation aspects vertically, as the system decoration is
+ * at the top and the bottom. For example when we animate from full screen to
+ * recents, we want to exclude the covered parts, because they won't match the
+ * thumbnail after the last frame is executed.
+ * @param surfaceInsets In rare situation the surface is larger than the content and we need to
+ * know about this to make the animation frames match. We currently use
+ * this for freeform windows, which have larger surfaces to display
+ * shadows. When we animate them from recents, we want to match the content
+ * to the recents thumbnail and hence need to account for the surface being
+ * bigger.
+ */
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
- @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
- boolean resizedWindow, int taskId) {
+ int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
+ boolean isVoiceInteraction, boolean freeform, int taskId) {
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
@@ -1086,7 +1120,7 @@
+ " anim=" + a + " transit=" + appTransitionToString(transit)
+ " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
} else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
- a = createRelaunchAnimation(appWidth, appHeight, containingFrame);
+ a = createRelaunchAnimation(frame, insets);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=" + mNextAppTransition
@@ -1108,14 +1142,14 @@
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = createClipRevealAnimationLocked(transit, enter, appFrame);
+ a = createClipRevealAnimationLocked(transit, enter, frame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+ " transit=" + appTransitionToString(transit)
+ " Callers=" + Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
- a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+ a = createScaleUpAnimationLocked(transit, enter, frame);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
@@ -1126,7 +1160,7 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
- appWidth, appHeight, transit, taskId);
+ frame, transit, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1140,8 +1174,8 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
a = createAspectScaledThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
- containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
+ getThumbnailTransitionState(enter), orientation, transit, frame,
+ insets, surfaceInsets, freeform, taskId);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index bf7063f..a61e83f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -300,7 +300,8 @@
return false;
}
- if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+ if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface
+ || animating || mAppToken.startingDisplayed)
&& animation != null) {
if (!animating) {
if (DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5a1ed58..943e3ea 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -52,6 +52,13 @@
final boolean voiceInteraction;
+ // Whether the window has a saved surface from last pause, which can be
+ // used to start an entering animation earlier.
+ boolean mHasSavedSurface;
+
+ // Whether we're performing an entering animation with a saved surface.
+ boolean mAnimatingWithSavedSurface;
+
Task mTask;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -114,11 +121,14 @@
// This application will have its window replaced due to relaunch. This allows window manager
// to differentiate between simple removal of a window and replacement. In the latter case it
// will preserve the old window until the new one is drawn.
- boolean mReplacingWindow;
+ boolean mWillReplaceWindow;
// If true, the replaced window was already requested to be removed.
boolean mReplacingRemoveRequested;
// Whether the replacement of the window should trigger app transition animation.
boolean mAnimateReplacingWindow;
+ // If not null, the window that will be used to replace the old one. This is being set when
+ // the window is added and unset when this window reports its first draw.
+ WindowState mReplacingWindow;
AppWindowToken(WindowManagerService _service, IApplicationToken _token,
boolean _voiceInteraction) {
@@ -257,10 +267,13 @@
final int N = allAppWindows.size();
for (int i=0; i<N; i++) {
WindowState win = allAppWindows.get(i);
+ // If we're animating with a saved surface, we're already visible.
+ // Return true so that the alpha doesn't get cleared.
if (!win.mAppFreezing
- && (win.mViewVisibility == View.VISIBLE ||
- (win.mWinAnimator.isAnimating() &&
- !service.mAppTransition.isTransitionSet()))
+ && (win.mViewVisibility == View.VISIBLE
+ || mAnimatingWithSavedSurface
+ || (win.mWinAnimator.isAnimating() &&
+ !service.mAppTransition.isTransitionSet()))
&& !win.mDestroying && win.isDrawnLw()) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6527881..4d11452 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.DOCKED_STACK_ID;
import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.HOME_STACK_ID;
import static android.app.ActivityManager.PINNED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static com.android.server.wm.WindowManagerService.TAG;
@@ -324,6 +325,10 @@
return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
}
+ boolean inHomeStack() {
+ return mStack != null && mStack.mStackId == HOME_STACK_ID;
+ }
+
boolean inFreeformWorkspace() {
return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 928a117..be86e2f 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -413,7 +413,7 @@
final AppWindowToken atoken = win.mAppToken;
if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
- if (atoken == null || atoken.allDrawn) {
+ if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) {
if (winAnimator.performShowLocked()) {
setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 05e51ca..8a2442a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1327,8 +1327,14 @@
addAttachedWindowToListLocked(win, addToToken);
}
- if (win.mAppToken != null && addToToken) {
- win.mAppToken.allAppWindows.add(win);
+ final AppWindowToken appToken = win.mAppToken;
+ if (appToken != null) {
+ if (addToToken) {
+ appToken.allAppWindows.add(win);
+ }
+ if (appToken.mWillReplaceWindow) {
+ appToken.mReplacingWindow = win;
+ }
}
}
@@ -2053,7 +2059,7 @@
}
private void prepareWindowReplacementTransition(AppWindowToken atoken) {
- if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) {
+ if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
return;
}
atoken.allDrawn = false;
@@ -2157,6 +2163,8 @@
+ " isAnimating=" + win.mWinAnimator.isAnimating()
+ " app-animation="
+ (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+ + " mWillReplaceWindow="
+ + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
+ " inPendingTransaction="
+ (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
+ " mDisplayFrozen=" + mDisplayFrozen);
@@ -2168,8 +2176,8 @@
// animation wouldn't be seen.
if (win.mHasSurface && okToDisplay()) {
final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mReplacingWindow) {
- // This window is going to be replaced. We need to kepp it around until the new one
+ if (appToken != null && appToken.mWillReplaceWindow) {
+ // This window is going to be replaced. We need to keep it around until the new one
// gets added, then we will get rid of this one.
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is "
+ "added");
@@ -2679,9 +2687,16 @@
if (winAnimator.mSurfaceControl != null) {
if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
+ ": mExiting=" + win.mExiting);
+ // If we are using a saved surface to do enter animation, just let the
+ // animation run and don't destroy the surface. This could happen when
+ // the app sets visibility to invisible for the first time after resume,
+ // or when the user exits immediately after a resume. In both cases, we
+ // don't want to destroy the saved surface.
+ final boolean isAnimatingWithSavedSurface =
+ win.mAppToken != null && win.mAppToken.mAnimatingWithSavedSurface;
// If we are not currently running the exit animation, we
// need to see about starting one.
- if (!win.mExiting) {
+ if (!win.mExiting && !isAnimatingWithSavedSurface) {
surfaceChanged = true;
// Try starting an animation; if there isn't one, we
// can destroy the surface right away.
@@ -2833,12 +2848,19 @@
try {
synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
- if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win);
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState="
+ + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
getDefaultDisplayContentLocked().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
+ if (win.mAppToken != null) {
+ // App has drawn something to its windows, we're no longer animating with
+ // the saved surfaces. If the user exits now, we only want to save again
+ // if allDrawn is true.
+ win.mAppToken.mAnimatingWithSavedSurface = false;
+ }
win.setDisplayLayoutNeeded();
mWindowPlacerLocked.requestTraversal();
}
@@ -2862,46 +2884,44 @@
"applyAnimation: atoken=" + atoken);
// Determine the visible rect to calculate the thumbnail clip
- WindowState win = atoken.findMainWindow();
- Rect containingFrame = new Rect(0, 0, width, height);
- Rect contentInsets = new Rect();
- Rect appFrame = new Rect(0, 0, width, height);
+ final WindowState win = atoken.findMainWindow();
+ final Rect frame = new Rect(0, 0, width, height);
+ final Rect insets = new Rect();
Rect surfaceInsets = null;
final boolean fullscreen = win != null && win.isFullscreen(width, height);
final boolean freeform = win != null && win.inFreeformWorkspace();
+ final boolean docked = win != null && win.inDockedWorkspace();
if (win != null) {
// Containing frame will usually cover the whole screen, including dialog windows.
// For freeform workspace windows it will not cover the whole screen and it also
// won't exactly match the final freeform window frame (e.g. when overlapping with
// the status bar). In that case we need to use the final frame.
if (freeform) {
- containingFrame.set(win.mFrame);
+ frame.set(win.mFrame);
} else {
- containingFrame.set(win.mContainingFrame);
+ frame.set(win.mContainingFrame);
}
surfaceInsets = win.getAttrs().surfaceInsets;
- if (fullscreen) {
+ if (fullscreen || docked) {
// For fullscreen windows use the window frames and insets to set the thumbnail
- // clip. For none-fullscreen windows we use the app display region so the clip
+ // clip. For non-fullscreen windows we use the app display region so the clip
// isn't affected by the window insets.
- contentInsets.set(win.mContentInsets);
- appFrame.set(win.mFrame);
- } else {
- appFrame.set(containingFrame);
+ insets.set(win.mContentInsets);
}
}
- final int containingWidth = containingFrame.width();
- final int containingHeight = containingFrame.height();
if (atoken.mLaunchTaskBehind) {
// Differentiate the two animations. This one which is briefly on the screen
// gets the !enter animation, and the other activity which remains on the
// screen gets the enter animation. Both appear in the mOpeningApps set.
enter = false;
}
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
- containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
- surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId);
+ if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition."
+ + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+ + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter,
+ mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
+ freeform, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -2911,6 +2931,8 @@
}
Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
}
+ final int containingWidth = frame.width();
+ final int containingHeight = frame.height();
atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
mAppTransition.canSkipFirstFrame());
}
@@ -3909,7 +3931,7 @@
// transition animation
// * or this is an opening app and windows are being replaced.
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
- (visible && wtoken.mReplacingWindow)) {
+ (visible && wtoken.mWillReplaceWindow)) {
boolean changed = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -8347,7 +8369,7 @@
final int numTokens = tokens.size();
for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mIsExiting && !wtoken.mReplacingWindow) {
+ if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
continue;
}
i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8417,7 +8439,8 @@
} else if (wtoken != null) {
winAnimator.mAnimLayer =
w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
- if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) {
+ if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
+ wtoken.mReplacingWindow != w) {
// We know that we will be animating a relaunching window in the near future,
// which will receive a z-order increase. We want the replaced window to
// immediately receive the same treatment, e.g. to be above the dock divider.
@@ -9963,7 +9986,9 @@
Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token);
return;
}
- appWindowToken.mReplacingWindow = true;
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken
+ + " as replacing window.");
+ appWindowToken.mWillReplaceWindow = true;
appWindowToken.mAnimateReplacingWindow = animate;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7ea64f1..e9ea3a8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,6 +28,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
@@ -35,6 +36,7 @@
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.graphics.Point;
import android.os.PowerManager;
@@ -55,7 +57,6 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
@@ -446,7 +447,8 @@
+ WindowManagerService.TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
mAttachedWindow = attachedWindow;
- if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
+ if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to "
+ + mAttachedWindow);
final WindowList childWindows = mAttachedWindow.mChildWindows;
final int numChildWindows = childWindows.size();
@@ -552,7 +554,7 @@
@Override
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
Rect osf) {
- if (mAppToken != null && mAppToken.mReplacingWindow
+ if (mAppToken != null && mAppToken.mWillReplaceWindow
&& (mExiting || !mAppToken.mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
@@ -1307,10 +1309,12 @@
void maybeRemoveReplacedWindow() {
AppWindowToken token = mAppToken;
- if (token != null && token.mReplacingWindow) {
- token.mReplacingWindow = false;
+ if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) {
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
+ token.mWillReplaceWindow = false;
token.mAnimateReplacingWindow = false;
token.mReplacingRemoveRequested = false;
+ token.mReplacingWindow = null;
for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
WindowState win = token.allAppWindows.get(i);
if (win.mExiting) {
@@ -1501,6 +1505,31 @@
return mExiting || (mService.mClosingApps.contains(mAppToken));
}
+ boolean saveSurfaceIfNeeded() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ // Don't save surfaces on Svelte devices.
+ return false;
+ }
+
+ Task task = getTask();
+ if (task == null || task.inHomeStack()) {
+ // Don't save surfaces for home stack apps. These usually resume and draw
+ // first frame very fast. Saving surfaces are mostly a waste of memory.
+ return false;
+ }
+
+ // We want to save surface if the app's windows are "allDrawn", or if we're
+ // currently animating with save surfaces. (If the app didn't even finish
+ // drawing when the user exits, but we have a saved surface from last time,
+ // we still want to keep that surface.)
+ if (mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface) {
+ mAppToken.mHasSavedSurface = true;
+ return true;
+ }
+
+ return false;
+ }
+
@Override
public boolean isDefaultDisplay() {
final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1754123..68c39ba 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -38,7 +38,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.os.Debug;
import android.os.RemoteException;
@@ -536,6 +535,7 @@
mDrawState = COMMIT_DRAW_PENDING;
return true;
}
+
return false;
}
@@ -555,7 +555,8 @@
mDrawState = READY_TO_SHOW;
boolean result = false;
final AppWindowToken atoken = mWin.mAppToken;
- if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface ||
+ mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
if (mDestroyPreservedSurfaceUponRedraw && result) {
@@ -996,12 +997,16 @@
}
void destroySurfaceLocked() {
- if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
- mWin.mAppToken.startingDisplayed = false;
+ final AppWindowToken wtoken = mWin.mAppToken;
+ if (wtoken != null) {
+ wtoken.mHasSavedSurface = false;
+ wtoken.mAnimatingWithSavedSurface = false;
+ if (mWin == wtoken.startingWindow) {
+ wtoken.startingDisplayed = false;
+ }
}
if (mSurfaceControl != null) {
-
int i = mWin.mChildWindows.size();
while (i > 0) {
i--;
@@ -1428,7 +1433,7 @@
// living in a different stack. If we suddenly crop it to the new stack bounds, it might
// get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
// gets removed. The window that will replace it will abide them.
- if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) {
+ if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
TaskStack stack = w.getTask().mStack;
stack.getBounds(mTmpStackBounds);
// When we resize we use the big surface approach, which means we can't trust the
@@ -1854,8 +1859,7 @@
}
}
- if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
- && mWin.mAppToken != null) {
+ if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
mWin.mAppToken.firstWindowDrawn = true;
if (mWin.mAppToken.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 0c004b2..e2f4dd9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,6 +17,7 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerService.DEBUG;
import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -340,6 +341,10 @@
// Don't remove this window until rotation has completed.
continue;
}
+ // Discard the saved surface if window size is changed, it can't be reused.
+ if (win.mAppToken != null && win.mAppToken.mHasSavedSurface) {
+ win.mWinAnimator.destroySurfaceLocked();
+ }
win.reportResized();
mService.mResizingWindows.remove(i);
}
@@ -371,7 +376,9 @@
if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
wallpaperDestroyed = true;
}
- win.mWinAnimator.destroySurfaceLocked();
+ if (!win.saveSurfaceIfNeeded()) {
+ win.mWinAnimator.destroySurfaceLocked();
+ }
} while (i > 0);
mService.mDestroySurface.clear();
}
@@ -1174,6 +1181,18 @@
}
}
createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+
+ if (wtoken.mHasSavedSurface) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "Early start of animation: " + wtoken + ", allDrawn=" + wtoken.allDrawn);
+
+ for (int j = 0; j < wtoken.windows.size(); j++) {
+ WindowState ws = wtoken.windows.get(j);
+ ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+ }
+ wtoken.mHasSavedSurface = false;
+ wtoken.mAnimatingWithSavedSurface = true;
+ }
}
AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null :
@@ -1219,6 +1238,10 @@
+ wtoken.allDrawn + " startingDisplayed="
+ wtoken.startingDisplayed + " startingMoved="
+ wtoken.startingMoved);
+
+ if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) {
+ continue;
+ }
if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
return false;
}