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

Commit 9f446be5 authored by Bryce Lee's avatar Bryce Lee
Browse files

Move communal surface handling to controller.

This changelist removes CommunalSurfaceView in
favor of using a controller to control the
lifecycle of a regular SurfaceView.

Bug: 195594431
Test: atest CommunalSurfaceViewControllerTest
Change-Id: I03c55bd479a98a2c5b75b12d1f40111e03f157e2
parent cb88b332
Loading
Loading
Loading
Loading
+9 −4
Original line number Diff line number Diff line
@@ -67,6 +67,8 @@ public class CommunalHostViewController extends ViewController<CommunalHostView>
    private static final int SHOW_COMMUNAL_VIEW_INVALID_STATES =
            STATE_DOZING | STATE_BOUNCER_SHOWING | STATE_KEYGUARD_OCCLUDED;

    private ViewController<? extends View> mCommunalViewController;

    private KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
            new KeyguardUpdateMonitorCallback() {
                @Override
@@ -220,7 +222,7 @@ public class CommunalHostViewController extends ViewController<CommunalHostView>

                final Context context = mView.getContext();

                final ListenableFuture<View> listenableFuture =
                final ListenableFuture<CommunalSource.CommunalViewResult> listenableFuture =
                        currentSource.requestCommunalView(context);

                if (listenableFuture == null) {
@@ -230,11 +232,14 @@ public class CommunalHostViewController extends ViewController<CommunalHostView>

                listenableFuture.addListener(() -> {
                    try {
                        final View view = listenableFuture.get();
                        view.setLayoutParams(new ViewGroup.LayoutParams(
                        final CommunalSource.CommunalViewResult result = listenableFuture.get();
                        result.view.setLayoutParams(new ViewGroup.LayoutParams(
                                ViewGroup.LayoutParams.MATCH_PARENT,
                                ViewGroup.LayoutParams.MATCH_PARENT));
                        mView.addView(view);
                        mView.addView(result.view);

                        mCommunalViewController = result.viewController;
                        mCommunalViewController.init();
                    } catch (Exception e) {
                        Log.e(TAG, "could not obtain communal view through callback:" + e);
                    }
+30 −3
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.communal;
import android.content.Context;
import android.view.View;

import com.android.systemui.util.ViewController;

import com.google.common.util.concurrent.ListenableFuture;

/**
@@ -27,14 +29,39 @@ import com.google.common.util.concurrent.ListenableFuture;
 * Callbacks may also be registered to listen to state changes.
 */
public interface CommunalSource {
    /**
     * {@link CommunalViewResult} is handed back from {@link #requestCommunalView(Context)} and
     * contains the view to be displayed and its associated controller.
     */
    class CommunalViewResult {
        /**
         * The resulting communal view.
         */
        public final View view;
        /**
         * The controller for the communal view.
         */
        public final ViewController<? extends View> viewController;

        /**
         * The default constructor for {@link CommunalViewResult}.
         * @param view The communal view.
         * @param viewController The communal view's controller.
         */
        public CommunalViewResult(View view, ViewController<? extends View> viewController) {
            this.view = view;
            this.viewController = viewController;
        }
    }

    /**
     * Requests a communal surface that can be displayed inside {@link CommunalHostView}.
     *
     * @param context The {@link View} {@link Context} to build the resulting view from
     * @return A future that can be listened upon for the resulting {@link View}. The value will be
     * {@code null} in case of a failure.
     * @return A future that can be listened upon for the resulting {@link CommunalViewResult}. The
     * value will be {@code null} in case of a failure.
     */
    ListenableFuture<View> requestCommunalView(Context context);
    ListenableFuture<CommunalViewResult> requestCommunalView(Context context);

    /**
     * Adds a {@link Callback} to receive future status updates regarding this
+6 −4
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.SurfaceView;

import androidx.concurrent.futures.CallbackToFutureAdapter;

@@ -106,13 +106,15 @@ public class CommunalSourceImpl implements CommunalSource {
    }

    @Override
    public ListenableFuture<View> requestCommunalView(Context context) {
    public ListenableFuture<CommunalViewResult> requestCommunalView(Context context) {
        if (DEBUG) {
            Log.d(TAG, "Received request for communal view");
        }
        ListenableFuture<View> packageFuture =
        ListenableFuture<CommunalViewResult> packageFuture =
                CallbackToFutureAdapter.getFuture(completer -> {
                    completer.set(new CommunalSurfaceView(context, mMainExecutor, this));
                    final SurfaceView view = new SurfaceView(context);
                    completer.set(new CommunalViewResult(view,
                            new CommunalSurfaceViewController(view, mMainExecutor, this)));
                    return "CommunalSourceImpl::requestCommunalSurface::getCommunalSurface";
                });

+153 −0
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

package com.android.systemui.communal.service;

import android.content.Context;
import android.annotation.IntDef;
import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
@@ -24,56 +24,103 @@ import android.view.SurfaceView;

import androidx.annotation.NonNull;

import com.android.systemui.util.ViewController;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.concurrent.Executor;

/**
 * {@link CommunalSurfaceView} can be used to display remote surfaces returned by the
 * {@link CommunalService}. The necessary information for the request are derived from the view's
 * events from being attached to the parent container.
 * {@link CommunalSurfaceViewController} coordinates requesting communal surfaces to populate a
 * {@link SurfaceView} with.
 */
public class CommunalSurfaceView extends SurfaceView {
    private static final String TAG = "CommunalSurfaceView";
    private static final boolean DEBUG = false;
public class CommunalSurfaceViewController extends ViewController<SurfaceView> {
    private static final String TAG = "CommunalSurfaceViewCtlr";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private final Executor mMainExecutor;
    private final CommunalSourceImpl mSource;

    public CommunalSurfaceView(Context context, Executor executor, CommunalSourceImpl source) {
        super(context);
        mSource = source;
        mMainExecutor = executor;
    @IntDef({STATE_SURFACE_CREATED, STATE_SURFACE_VIEW_ATTACHED})
    private @interface State {}

    private static final int STATE_SURFACE_CREATED = 1 << 0;
    private static final int STATE_SURFACE_VIEW_ATTACHED = 1 << 1;

    private static final int STATE_CAN_SHOW_SURFACE =
            STATE_SURFACE_CREATED | STATE_SURFACE_VIEW_ATTACHED;

        getHolder().addCallback(new SurfaceHolder.Callback() {
    private int mCurrentState;

    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
                onSurfaceCreated();
            setState(STATE_SURFACE_CREATED, true);
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
                int height) {

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            setState(STATE_SURFACE_CREATED, false);
        }
    };

    protected CommunalSurfaceViewController(SurfaceView view, Executor executor,
            CommunalSourceImpl source) {
        super(view);
        mSource = source;
        mMainExecutor = executor;
    }

    @Override
    public void init() {
        super.init();
        mView.getHolder().addCallback(mSurfaceHolderCallback);
    }
        });

    private void setState(@State int state, boolean enabled) {
        if (DEBUG) {
            Log.d(TAG, "setState. state:" + state + " enable:" + enabled);
        }

    private void onSurfaceCreated() {
        setWillNotDraw(false);
        final int newState = enabled ? mCurrentState | state : mCurrentState & ~state;

        // no new state is available
        if (newState == mCurrentState) {
            return;
        }

        if (DEBUG) {
            Log.d(TAG, "setState. new state:" + mCurrentState);
        }

        mCurrentState = newState;

        if (newState == STATE_CAN_SHOW_SURFACE) {
            showSurface();
        }
    }

    private void showSurface() {
        mView.setWillNotDraw(false);

        final ListenableFuture<SurfaceControlViewHost.SurfacePackage> surfaceFuture =
                mSource.requestCommunalSurface(this.getHostToken(),
                        getDisplay().getDisplayId(), getMeasuredWidth(), getMeasuredHeight());
                mSource.requestCommunalSurface(mView.getHostToken(),
                        mView.getDisplay().getDisplayId(), mView.getMeasuredWidth(),
                        mView.getMeasuredHeight());

        surfaceFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    // If the request is received after detached, ignore.
                    if (!mView.isAttachedToWindow()) {
                        return;
                    }

                    SurfaceControlViewHost.SurfacePackage surfacePackage = surfaceFuture.get();

                    if (DEBUG) {
@@ -81,9 +128,9 @@ public class CommunalSurfaceView extends SurfaceView {
                    }

                    if (surfacePackage != null) {
                        setChildSurfacePackage(surfacePackage);
                        setZOrderOnTop(true);
                        postInvalidate();
                        mView.setChildSurfacePackage(surfacePackage);
                        mView.setZOrderOnTop(true);
                        mView.postInvalidate();
                    } else {
                        Log.e(TAG, "couldn't get the surface package");
                    }
@@ -93,4 +140,14 @@ public class CommunalSurfaceView extends SurfaceView {
            }
        }, mMainExecutor);
    }

    @Override
    protected void onViewAttached() {
        setState(STATE_SURFACE_VIEW_ATTACHED, true);
    }

    @Override
    protected void onViewDetached() {
        setState(STATE_SURFACE_VIEW_ATTACHED, false);
    }
}
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.communal.service;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.IBinder;
import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import com.google.common.util.concurrent.SettableFuture;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@SmallTest
public class CommunalSurfaceViewControllerTest extends SysuiTestCase {
    private static final int MEASURED_HEIGHT = 200;
    private static final int MEASURED_WIDTH = 500;
    private static final int DISPLAY_ID = 3;

    @Mock
    private Display mDisplay;

    @Mock
    private IBinder mHostToken;

    @Mock
    private SurfaceView mSurfaceView;

    @Mock
    private SurfaceHolder mSurfaceHolder;

    @Mock
    private CommunalSourceImpl mCommunalSource;

    @Mock
    private SurfaceControlViewHost.SurfacePackage mSurfacePackage;

    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());

    private SurfaceHolder.Callback mCallback;

    private CommunalSurfaceViewController mController;

    private SettableFuture<SurfaceControlViewHost.SurfacePackage> mPackageFuture;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        final ArgumentCaptor<SurfaceHolder.Callback> callbackCapture =
                ArgumentCaptor.forClass(SurfaceHolder.Callback.class);
        when(mSurfaceView.getHolder()).thenReturn(mSurfaceHolder);
        when(mSurfaceView.getDisplay()).thenReturn(mDisplay);
        when(mDisplay.getDisplayId()).thenReturn(DISPLAY_ID);
        when(mSurfaceView.getHostToken()).thenReturn(mHostToken);
        when(mSurfaceView.getMeasuredWidth()).thenReturn(MEASURED_WIDTH);
        when(mSurfaceView.getMeasuredHeight()).thenReturn(MEASURED_HEIGHT);
        when(mSurfaceView.isAttachedToWindow()).thenReturn(false);
        mController = new CommunalSurfaceViewController(mSurfaceView, mFakeExecutor,
                mCommunalSource);
        mController.init();
        verify(mSurfaceHolder).addCallback(callbackCapture.capture());
        mCallback = callbackCapture.getValue();

        mPackageFuture = SettableFuture.create();

        when(mCommunalSource.requestCommunalSurface(any(), anyInt(), anyInt(), anyInt()))
                .thenReturn(mPackageFuture);
    }

    @Test
    public void testSetSurfacePackage() {
        // There should be no requests without the proper state.
        verify(mCommunalSource, times(0))
                .requestCommunalSurface(any(), anyInt(), anyInt(), anyInt());

        // The full state must be present to make a request.
        mController.onViewAttached();
        verify(mCommunalSource, times(0))
                .requestCommunalSurface(any(), anyInt(), anyInt(), anyInt());

        // Request surface view once all conditions are met.
        mCallback.surfaceCreated(mSurfaceHolder);
        verify(mCommunalSource)
                .requestCommunalSurface(mHostToken, DISPLAY_ID, MEASURED_WIDTH, MEASURED_HEIGHT);

        when(mSurfaceView.isAttachedToWindow()).thenReturn(true);

        // Respond to request.
        mPackageFuture.set(mSurfacePackage);
        mFakeExecutor.runAllReady();


        // Make sure SurfaceView is set.
        verify(mSurfaceView).setChildSurfacePackage(mSurfacePackage);
        verify(mSurfaceView).setZOrderOnTop(true);
        verify(mSurfaceView).setWillNotDraw(false);
    }
}
Loading