Invoke setWindowStopped on the thread that created the view

The windows belong to a activity will be set to stopped state
when the activity is stopped. But the associated thread of
view root could be different from activity, that results the
inconsistent calling thread of surface callback, and maybe
some potential races.

Since the surface could be destroyed during traversal, also
add a check for callback notifySurfaceDestroyed to prevent
duplicated calls if the surface is released.

Bug: 130553315
Test: atest ViewRootSurfaceCallbackTest
Change-Id: Iea2cf8ff51118c0fa8c00a35c1d3e53c959ea754
This commit is contained in:
Riddle Hsu
2019-04-29 19:22:38 +08:00
parent c3c30eefb2
commit 5828b413b0
3 changed files with 126 additions and 2 deletions

View File

@@ -1522,6 +1522,7 @@ public final class ViewRootImpl implements ViewParent,
}
void setWindowStopped(boolean stopped) {
checkThread();
if (mStopped != stopped) {
mStopped = stopped;
final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
@@ -1543,7 +1544,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mStopped) {
if (mSurfaceHolder != null) {
if (mSurfaceHolder != null && mSurface.isValid()) {
notifySurfaceDestroyed();
}
destroySurface();

View File

@@ -636,13 +636,21 @@ public final class WindowManagerGlobal {
}
public void setStoppedState(IBinder token, boolean stopped) {
ArrayList<ViewRootImpl> nonCurrentThreadRoots = null;
synchronized (mLock) {
int count = mViews.size();
for (int i = count - 1; i >= 0; i--) {
if (token == null || mParams.get(i).token == token) {
ViewRootImpl root = mRoots.get(i);
// Client might remove the view by "stopped" event.
root.setWindowStopped(stopped);
if (root.mThread == Thread.currentThread()) {
root.setWindowStopped(stopped);
} else {
if (nonCurrentThreadRoots == null) {
nonCurrentThreadRoots = new ArrayList<>();
}
nonCurrentThreadRoots.add(root);
}
// Recursively forward stopped state to View's attached
// to this Window rather than the root application token,
// e.g. PopupWindow's.
@@ -650,6 +658,16 @@ public final class WindowManagerGlobal {
}
}
}
// Update the stopped state synchronously to ensure the surface won't be used after server
// side has destroyed it. This operation should be outside the lock to avoid any potential
// paths from setWindowStopped to WindowManagerGlobal which may cause deadlocks.
if (nonCurrentThreadRoots != null) {
for (int i = nonCurrentThreadRoots.size() - 1; i >= 0; i--) {
ViewRootImpl root = nonCurrentThreadRoots.get(i);
root.mHandler.runWithScissors(() -> root.setWindowStopped(stopped), 0);
}
}
}
public void reportNewConfiguration(Configuration config) {

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2019 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 android.view;
import static android.os.Process.myTid;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.HandlerThread;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import org.junit.Rule;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@SmallTest
public class ViewRootSurfaceCallbackTest implements SurfaceHolder.Callback2 {
private static final String TAG = ViewRootSurfaceCallbackTest.class.getSimpleName();
private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
@Rule
public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
private final CompletableFuture<Integer> mTidOfSurfaceCreated = new CompletableFuture<>();
private final CompletableFuture<Integer> mTidOfSurfaceDestroyed = new CompletableFuture<>();
private boolean mDuplicatedSurfaceDestroyed;
/**
* Verifies that the calling thread of {@link SurfaceHolder.Callback2} should be the same as the
* thread that created the view root.
*/
@Test
public void testCallingTidOfSurfaceCallback() throws Exception {
final Activity activity = mActivityRule.getActivity();
activity.setTurnScreenOn(true);
activity.setShowWhenLocked(true);
// Create a dialog that runs on another thread and let it handle surface by itself.
final HandlerThread thread = new HandlerThread(TAG);
thread.start();
thread.getThreadHandler().runWithScissors(() -> {
final AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle(TAG).setMessage(TAG).create();
dialog.getWindow().takeSurface(this);
dialog.show();
}, TIMEOUT_MS);
final int attachedTid = thread.getThreadId();
assertEquals(attachedTid,
mTidOfSurfaceCreated.get(TIMEOUT_MS, TimeUnit.MILLISECONDS).intValue());
// Make the activity invisible.
activity.moveTaskToBack(true /* nonRoot */);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertEquals(attachedTid,
mTidOfSurfaceDestroyed.get(TIMEOUT_MS, TimeUnit.MILLISECONDS).intValue());
assertFalse("surfaceDestroyed should not be called twice", mDuplicatedSurfaceDestroyed);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mTidOfSurfaceCreated.complete(myTid());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (!mTidOfSurfaceDestroyed.isDone()) {
mTidOfSurfaceDestroyed.complete(myTid());
} else {
mDuplicatedSurfaceDestroyed = true;
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
}
}