Merge "Move communal surface handling to controller."

This commit is contained in:
Bryce Lee
2021-08-17 03:31:28 +00:00
committed by Android (Google) Code Review
6 changed files with 327 additions and 107 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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";
});

View File

@@ -1,96 +0,0 @@
/*
* 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 android.content.Context;
import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
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.
*/
public class CommunalSurfaceView extends SurfaceView {
private static final String TAG = "CommunalSurfaceView";
private static final boolean DEBUG = false;
private final Executor mMainExecutor;
private final CommunalSourceImpl mSource;
public CommunalSurfaceView(Context context, Executor executor, CommunalSourceImpl source) {
super(context);
mSource = source;
mMainExecutor = executor;
getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
onSurfaceCreated();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
}
private void onSurfaceCreated() {
setWillNotDraw(false);
final ListenableFuture<SurfaceControlViewHost.SurfacePackage> surfaceFuture =
mSource.requestCommunalSurface(this.getHostToken(),
getDisplay().getDisplayId(), getMeasuredWidth(), getMeasuredHeight());
surfaceFuture.addListener(new Runnable() {
@Override
public void run() {
try {
SurfaceControlViewHost.SurfacePackage surfacePackage = surfaceFuture.get();
if (DEBUG) {
Log.d(TAG, "Received surface package:" + surfacePackage);
}
if (surfacePackage != null) {
setChildSurfacePackage(surfacePackage);
setZOrderOnTop(true);
postInvalidate();
} else {
Log.e(TAG, "couldn't get the surface package");
}
} catch (Exception e) {
Log.e(TAG, "An error occurred retrieving the future result:" + e);
}
}
}, mMainExecutor);
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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 android.annotation.IntDef;
import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
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 CommunalSurfaceViewController} coordinates requesting communal surfaces to populate a
* {@link SurfaceView} with.
*/
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;
@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;
private int mCurrentState;
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
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);
}
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(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) {
Log.d(TAG, "Received surface package:" + surfacePackage);
}
if (surfacePackage != null) {
mView.setChildSurfacePackage(surfacePackage);
mView.setZOrderOnTop(true);
mView.postInvalidate();
} else {
Log.e(TAG, "couldn't get the surface package");
}
} catch (Exception e) {
Log.e(TAG, "An error occurred retrieving the future result:" + e);
}
}
}, mMainExecutor);
}
@Override
protected void onViewAttached() {
setState(STATE_SURFACE_VIEW_ATTACHED, true);
}
@Override
protected void onViewDetached() {
setState(STATE_SURFACE_VIEW_ATTACHED, false);
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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);
}
}