Loading packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java +9 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); } Loading packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java +30 −3 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java +6 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; }); Loading packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceView.java→packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java +153 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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"); } Loading @@ -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); } } packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java 0 → 100644 +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
packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java +9 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); } Loading
packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java +30 −3 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java +6 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; }); Loading
packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceView.java→packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java +153 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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"); } Loading @@ -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); } }
packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java 0 → 100644 +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); } }