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

Commit 9312e1ad authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Move communal surface handling to controller."

parents 0ad52c2f 9f446be5
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