Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52976ae3 authored by Chavi Weingarten's avatar Chavi Weingarten
Browse files

Add TrustedPresentationCallback API for AttachedSurfaceControls

The TrustedPresentationCallback is a public API but need to be
registered on the SurfaceControl that is rendering content. This means
that windows can't register a TPC because their rendering SC is not
exposed. Add an additional API that allows the TPC to be called on
AttachedSurfaceControl so the listener can be registered on the
rendering SC.

Test: TrustedPresentationCallbackTest#testSetTrustedPresentationListenerOnWindow
Bug: 278027319
Bug: 287076178
Bug: 279957280
Change-Id: I9c6cc4745c0dcbbabc0477ada2faba9ca597a9d7
parent b0864b83
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.window.SurfaceSyncGroup;

import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * Provides an interface to the root-Surface of a View Hierarchy or Window. This
 * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -167,4 +170,40 @@ public interface AttachedSurfaceControl {
     */
    default void setChildBoundingInsets(@NonNull Rect insets) {
    }

    /**
     * Add a trusted presentation listener on the SurfaceControl associated with this window.
     *
     * @param t          Transaction that the trusted presentation listener is added on. This should
     *                   be applied by the caller.
     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
     *                   when the to invoke the callback.
     * @param executor   The {@link Executor} where the callback will be invoked on.
     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
     *                   exited the threshold.
     *
     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
     *
     * @hide b/287076178 un-hide with API bump
     */
    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
    }

    /**
     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
     *
     * @param t          Transaction that the trusted presentation listener removed on. This should
     *                   be applied by the caller.
     * @param listener   The {@link Consumer} that was previously registered with
     *                   addTrustedPresentationCallback that should be removed.
     *
     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
     * @hide b/287076178 un-hide with API bump
     */
    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
            @NonNull Consumer<Boolean> listener) {
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -11554,4 +11554,17 @@ public final class ViewRootImpl implements ViewParent,
        mChildBoundingInsetsChanged = true;
        scheduleTraversals();
    }

    @Override
    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
    }

    @Override
    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
            @NonNull Consumer<Boolean> listener) {
        t.clearTrustedPresentationCallback(getSurfaceControl());
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -96,6 +96,19 @@
            android:theme="@style/WhiteBackgroundTheme"
            android:exported="true"/>

        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
            android:exported="true"
            android:showWhenLocked="true"
            android:turnScreenOn="true" />

        <activity
            android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:enabled="true">
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.server.wm;

import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.app.Activity;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceControl.TrustedPresentationThresholds;

import androidx.test.ext.junit.rules.ActivityScenarioRule;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * TODO (b/287076178): Move these tests to
 * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
 */
@Presubmit
public class TrustedPresentationCallbackTest {
    private static final String TAG = "TrustedPresentationCallbackTest";
    private static final int STABILITY_REQUIREMENT_MS = 500;
    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;

    private static final float FRACTION_VISIBLE = 0.1f;

    @Rule
    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
            TestActivity.class);

    private TestActivity mActivity;

    @Before
    public void setup() {
        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
    }

    @Test
    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
        boolean[] results = new boolean[1];
        CountDownLatch receivedResults = new CountDownLatch(1);
        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
                Runnable::run, inTrustedPresentationState -> {
                    Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
                    results[0] = inTrustedPresentationState;
                    receivedResults.countDown();
                });
        t.apply();

        assertTrue("Timed out waiting for results",
                receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        assertTrue(results[0]);
    }

    @Test
    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
        final Object resultsLock = new Object();
        boolean[] results = new boolean[1];
        boolean[] receivedResults = new boolean[1];
        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
            synchronized (resultsLock) {
                results[0] = inTrustedPresentationState;
                receivedResults[0] = true;
                resultsLock.notify();
            }
        };
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
                Runnable::run, trustedPresentationCallback);
        t.apply();

        synchronized (resultsLock) {
            if (!receivedResults[0]) {
                resultsLock.wait(WAIT_TIME_MS);
            }
            // Make sure we received the results and not just timed out
            assertTrue("Timed out waiting for results", receivedResults[0]);
            assertTrue(results[0]);

            // reset the state
            receivedResults[0] = false;
        }

        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
                trustedPresentationCallback);
        t.apply();

        synchronized (resultsLock) {
            if (!receivedResults[0]) {
                resultsLock.wait(WAIT_TIME_MS);
            }
            // Ensure we waited the full time and never received a notify on the result from the
            // callback.
            assertFalse("Should never have received a callback", receivedResults[0]);
            // results shouldn't have changed.
            assertTrue(results[0]);
        }
    }

    public static class TestActivity extends Activity {
    }
}