From 804baccc3aeed4ed00d1f0a62ecc37fb5ea6d9fd Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Sun, 20 Jul 2014 09:48:43 -0700 Subject: Video call surfaces and changing to video UI on videoState change. Bug: 16012946 Change-Id: I1e86b172d46d3e67eff210d9f56e03a2e6c93853 --- InCallUI/src/com/android/incallui/Call.java | 5 +- .../src/com/android/incallui/CallCardFragment.java | 10 + .../com/android/incallui/CallCardPresenter.java | 6 + .../com/android/incallui/VideoCallFragment.java | 322 +++++++++++++++++++++ .../com/android/incallui/VideoCallPresenter.java | 267 +++++++++++++++++ 5 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 InCallUI/src/com/android/incallui/VideoCallFragment.java create mode 100644 InCallUI/src/com/android/incallui/VideoCallPresenter.java (limited to 'InCallUI/src') diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java index f6c039138..2ea80a300 100644 --- a/InCallUI/src/com/android/incallui/Call.java +++ b/InCallUI/src/com/android/incallui/Call.java @@ -335,11 +335,12 @@ public final class Call { @Override public String toString() { - return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s]", + return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, videoState:%d]", mId, State.toString(mState), CallCapabilities.toString(mTelecommCall.getDetails().getCapabilities()), mChildCallIds, - mParentCallId); + mParentCallId, + mTelecommCall.getDetails().getVideoState()); } } diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index 5080da048..f8dabf5ed 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -625,6 +625,16 @@ public class CallCardFragment extends BaseFragment eventText = event.getText(); diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index eb4640312..cbaeda6c3 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -201,6 +201,11 @@ public class CallCardPresenter extends Presenter getUi().setCallState(callState, DisconnectCause.NOT_VALID, null, null, null); } + // Hide/show the contact photo depending if this is a video call + if (mPrimary != null) { + getUi().setPhotoVisible(!mPrimary.isVideoCall()); + } + final boolean enableEndCallButton = Call.State.isConnected(callState) && callState != Call.State.INCOMING && mPrimary != null; getUi().setEndCallButtonEnabled(enableEndCallButton); @@ -571,6 +576,7 @@ public class CallCardPresenter extends Presenter void setEndCallButtonEnabled(boolean enabled); void setEmergencyCallbackNumber(String number); void setCallDetails(android.telecomm.Call.Details details); + void setPhotoVisible(boolean isVisible); } private TelecommManager getTelecommManager() { diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java new file mode 100644 index 000000000..eee9643f2 --- /dev/null +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2014 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.incallui; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewStub; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Fragment containing video calling surfaces. + */ +public class VideoCallFragment extends BaseFragment implements VideoCallPresenter.VideoCallUi { + + public static final int SURFACE_DISPLAY = 1; + public static final int SURFACE_PREVIEW = 2; + + /** + * Listener interface used by classes interested in changed to the video telephony surfaces + * in the {@link CallCardFragment}. + */ + public interface VideoCallSurfaceListener { + void onSurfaceCreated(int surface); + void onSurfaceDestroyed(int surface); + void onSurfaceChanged(int surface, int format, int width, int height); + } + + /** + * Listeners to video surface changes. + */ + private final Set mListeners = new CopyOnWriteArraySet<>(); + + /** + * {@link ViewStub} holding the video call surfaces. This is the parent for the + * {@link VideoCallFragment}. Used to ensure that the video surfaces are only inflated when + * required. + */ + private ViewStub mVideoViewsStub; + + /** + * Inflated view containing the video call surfaces represented by the {@link ViewStub}. + */ + private View mVideoViews; + + /** + * The display video {@link SurfaceView}. Incoming video from the remote party of the video + * call is displayed here. + */ + private SurfaceView mDisplayVideoSurface; + + /** + * The surface holder for the display surface. Provides access to the underlying + * {@link Surface} in the {@link SurfaceView} and allows listening to surface related events. + */ + private SurfaceHolder mDisplayVideoSurfaceHolder; + + /** + * Determines if the display surface has been created or not. + */ + private boolean mDisplayVideoSurfaceCreated; + + /** + * The preview video {@link SurfaceView}. A preview of the outgoing video to the remote party + * of the video call is displayed here. + */ + private SurfaceView mPreviewVideoSurface; + + /** + * The surface holder for the preview surface. Provides access to the underlying + * {@link Surface} in the {@link SurfaceView} and allows listening to surface related events. + */ + private SurfaceHolder mPreviewVideoSurfaceHolder; + + /** + * Determines if the preview surface has been created or not. + */ + private boolean mPreviewVideoSurfaceCreated; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + /** + * Handles creation of the activity and initialization of the presenter. + * + * @param savedInstanceState The saved instance state. + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + getPresenter().init(getActivity()); + } + + /** + * Handles creation of the fragment view. + * + * @param inflater The inflater. + * @param container The view group containing the fragment. + * @param savedInstanceState The saved instance state. + * @return + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + return inflater.inflate(R.layout.video_call_fragment, container, false); + } + + /** + * After creation of the fragment view, retrieves the required views. + * + * @param view The fragment view. + * @param savedInstanceState The saved instance state. + */ + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub); + } + + /** + * Creates the presenter for the {@link VideoCallFragment}. + * @return The presenter instance. + */ + @Override + public VideoCallPresenter createPresenter() { + return new VideoCallPresenter(); + } + + /** + * @return The user interface for the presenter, which is this fragment. + */ + @Override + VideoCallPresenter.VideoCallUi getUi() { + return this; + } + + /** + * SurfaceHolder callback used to track lifecycle changes to the surfaces. + */ + private SurfaceHolder.Callback mSurfaceHolderCallBack = new SurfaceHolder.Callback() { + /** + * Called immediately after the surface is first created. + * + * @param holder The surface holder. + */ + @Override + public void surfaceCreated(SurfaceHolder holder) { + int surfaceId = getSurfaceId(holder); + + if (surfaceId == SURFACE_DISPLAY) { + mDisplayVideoSurfaceCreated = true; + } else { + mPreviewVideoSurfaceCreated = true; + } + + getPresenter().onSurfaceCreated(surfaceId); + } + + /** + * Called immediately after any structural changes (format or size) have been made to the + * surface. + * + * @param holder The surface holder. + * @param format + * @param width + * @param height + */ + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d(this, "surfaceChanged %s", holder.getSurface().toString()); + getPresenter().onSurfaceChanged(getSurfaceId(holder), format, width, height); + } + + /** + * Called immediately before a surface is being destroyed. + * + * @param holder The surface holder. + */ + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(this, "surfaceDestroyed %s", holder.getSurface().toString()); + + int surfaceId = getSurfaceId(holder); + + if (surfaceId == SURFACE_DISPLAY) { + mDisplayVideoSurfaceCreated = false; + } else { + mPreviewVideoSurfaceCreated = false; + } + + getPresenter().onSurfaceDestroyed(surfaceId); + } + + /** + * Determines the surface ID for a specified surface. + * + * @param holder The surface holder. + * @return The surface ID. + */ + private int getSurfaceId(SurfaceHolder holder) { + int surface; + if (holder == mDisplayVideoSurface.getHolder()) { + Log.d(this, "surfaceCreated: DISPLAY"); + surface = SURFACE_DISPLAY; + } else { + Log.d(this, "surfaceCreated: PREVIEW"); + surface = SURFACE_PREVIEW; + } + return surface; + } + }; + + /** + * Toggles visibility of the video UI. + * + * @param show {@code True} if the video surfaces should be shown. + */ + @Override + public void showVideoUi(boolean show) { + getView().setVisibility(show ? View.VISIBLE : View.GONE); + + Log.d(this, "Show video call UI: " + show); + if (show) { + inflateVideoCallViews(); + } + + if (mVideoViews != null ) { + int newVisibility = show ? View.VISIBLE : View.GONE; + mVideoViews.setVisibility(newVisibility); + mDisplayVideoSurface.setVisibility(newVisibility); + mPreviewVideoSurface.setVisibility(newVisibility); + mPreviewVideoSurface.setZOrderOnTop(show); + } + } + + /** + * @return {@code True} if the display video surface has been created. + */ + @Override + public boolean isDisplayVideoSurfaceCreated() { + return mDisplayVideoSurfaceCreated; + } + + /** + * @return {@code True} if the preview video surface has been created. + */ + @Override + public boolean isPreviewVideoSurfaceCreated() { + return mPreviewVideoSurfaceCreated; + } + + /** + * {@link android.view.Surface} on which incoming video for a video call is displayed. + * {@code Null} until the video views {@link android.view.ViewStub} is inflated. + */ + public Surface getDisplayVideoSurface() { + if (mDisplayVideoSurfaceHolder != null) { + return mDisplayVideoSurfaceHolder.getSurface(); + } + return null; + } + + /** + * {@link android.view.Surface} on which a preview of the outgoing video for a video call is + * displayed. {@code Null} until the video views {@link android.view.ViewStub} is inflated. + */ + public Surface getPreviewVideoSurface() { + if (mPreviewVideoSurfaceHolder != null) { + return mPreviewVideoSurfaceHolder.getSurface(); + } + return null; + } + + /** + * Inflates the {@link ViewStub} containing the incoming and outgoing video surfaces and sets + * up a callback to listen for lifecycle changes to the surface. + */ + private void inflateVideoCallViews() { + if (mDisplayVideoSurface == null && mPreviewVideoSurface == null && mVideoViews == null ) { + mVideoViews = mVideoViewsStub.inflate(); + + if (mVideoViews != null) { + mDisplayVideoSurface = (SurfaceView) mVideoViews.findViewById(R.id.incomingVideo); + mDisplayVideoSurfaceHolder = mDisplayVideoSurface.getHolder(); + mDisplayVideoSurfaceHolder.addCallback(mSurfaceHolderCallBack); + + mPreviewVideoSurface = (SurfaceView) mVideoViews.findViewById(R.id.previewVideo); + mPreviewVideoSurfaceHolder = mPreviewVideoSurface.getHolder(); + mPreviewVideoSurfaceHolder.addCallback(mSurfaceHolderCallBack); + mPreviewVideoSurface.setZOrderOnTop(true); + } + } + } +} diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java new file mode 100644 index 000000000..0c8034cb3 --- /dev/null +++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2014 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.incallui; + +import com.android.internal.util.Preconditions; + +import android.content.Context; +import android.telecomm.RemoteCallVideoProvider; +import android.view.Surface; + +import java.util.Objects; + +/** + * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling + * surfaces based on other user interface events and incoming events from the + * {@class CallVideoClient}. + */ +public class VideoCallPresenter extends Presenter implements + InCallPresenter.IncomingCallListener, InCallPresenter.InCallStateListener { + + /** + * The current context. + */ + private Context mContext; + + /** + * The call the video surfaces are currently related to + */ + private Call mCall; + + /** + * The {@link RemoteCallVideoProvider} used to inform the video telephony layer of changes + * to the video surfaces. + */ + private RemoteCallVideoProvider mCallVideoProvider; + + /** + * Determines if the current UI state represents a video call. + */ + private boolean mIsVideoCall; + + /** + * Initializes the presenter. + * + * @param context The current context. + */ + public void init(Context context) { + mContext = Preconditions.checkNotNull(context); + } + + /** + * Called when the user interface is ready to be used. + * + * @param ui The Ui implementation that is now ready to be used. + */ + @Override + public void onUiReady(VideoCallUi ui) { + super.onUiReady(ui); + + // Register for call state changes last + InCallPresenter.getInstance().addListener(this); + InCallPresenter.getInstance().addIncomingCallListener(this); + mIsVideoCall = false; + } + + /** + * @return The {@link RemoteCallVideoProvider}. + */ + private RemoteCallVideoProvider getCallVideoProvider() { + return mCallVideoProvider; + } + + /** + * Handles the creation of a surface in the {@link VideoCallFragment}. + * + * @param surface The surface which was created. + */ + public void onSurfaceCreated(int surface) { + final VideoCallUi ui = getUi(); + final RemoteCallVideoProvider callVideoProvider = getCallVideoProvider(); + + if (ui == null || callVideoProvider == null) { + return; + } + + if (surface == VideoCallFragment.SURFACE_DISPLAY) { + mCallVideoProvider.setDisplaySurface(ui.getDisplayVideoSurface()); + } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { + mCallVideoProvider.setPreviewSurface(ui.getPreviewVideoSurface()); + } + } + + /** + * Handles structural changes (format or size) to a surface. + * + * @param surface The surface which changed. + * @param format The new PixelFormat of the surface. + * @param width The new width of the surface. + * @param height The new height of the surface. + */ + public void onSurfaceChanged(int surface, int format, int width, int height) { + //Do stuff + } + + /** + * Handles the destruction of a surface in the {@link VideoCallFragment}. + * + * @param surface The surface which was destroyed. + */ + public void onSurfaceDestroyed(int surface) { + final VideoCallUi ui = getUi(); + final RemoteCallVideoProvider callVideoProvider = getCallVideoProvider(); + + if (ui == null || callVideoProvider == null) { + return; + } + + if (surface == VideoCallFragment.SURFACE_DISPLAY) { + mCallVideoProvider.setDisplaySurface(null); + } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { + mCallVideoProvider.setPreviewSurface(null); + } + } + + /** + * Handles incoming calls. + * + * @param state The in call state. + * @param call The call. + */ + @Override + public void onIncomingCall(InCallPresenter.InCallState state, Call call) { + // same logic should happen as with onStateChange() + onStateChange(state, CallList.getInstance()); + } + + /** + * Handles state changes (including incoming calls) + * + * @param state The in call state. + * @param callList The call list. + */ + @Override + public void onStateChange(InCallPresenter.InCallState state, CallList callList) { + Call call = null; + if (state == InCallPresenter.InCallState.INCOMING) { + call = callList.getIncomingCall(); + } else if (state == InCallPresenter.InCallState.OUTGOING) { + call = callList.getOutgoingCall(); + } + + if (call == null || getUi() == null) { + return; + } + final boolean callChanged = !Objects.equals(mCall, call); + + // If the call changed track it now. + if (callChanged) { + mCall = call; + } + + RemoteCallVideoProvider callVideoProvider = + mCall.getTelecommCall().getCallVideoProvider(); + if (callVideoProvider != mCallVideoProvider) { + changeCallVideoProvider(callVideoProvider); + } + + boolean newVideoState = call.isVideoCall(); + + // Check if video state changed + if (mIsVideoCall != newVideoState) { + mIsVideoCall = newVideoState; + + if (mIsVideoCall) { + enterVideoState(); + } else { + exitVideoState(); + } + } + } + + /** + * Handles a change to the call video provider. Sets the surfaces on the previous provider + * to null and sets the surfaces on the new provider accordingly. + * + * @param callVideoProvider The new call video provider. + */ + private void changeCallVideoProvider(RemoteCallVideoProvider callVideoProvider) { + // Null out the surfaces on the previous provider + if (mCallVideoProvider != null) { + mCallVideoProvider.setDisplaySurface(null); + mCallVideoProvider.setPreviewSurface(null); + } + + mCallVideoProvider = callVideoProvider; + setSurfaces(); + + } + + /** + * Enters video mode by showing the video surfaces. + * TODO(vt): Need to adjust size and orientation of preview surface here. + */ + private void enterVideoState() { + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + ui.showVideoUi(true); + } + + /** + * Sets the surfaces on the specified {@link RemoteCallVideoProvider}. + */ + private void setSurfaces() { + VideoCallUi ui = getUi(); + if (ui == null || mCallVideoProvider == null) { + return; + } + + if (getUi().isDisplayVideoSurfaceCreated()) { + mCallVideoProvider.setDisplaySurface(ui.getDisplayVideoSurface()); + } + + if (getUi().isPreviewVideoSurfaceCreated()) { + mCallVideoProvider.setPreviewSurface(ui.getPreviewVideoSurface()); + } + } + + /** + * Exits video mode by hiding the video surfaces. + */ + private void exitVideoState() { + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + ui.showVideoUi(false); + } + + /** + * Defines the VideoCallUI interactions. + */ + public interface VideoCallUi extends Ui { + void showVideoUi(boolean show); + boolean isDisplayVideoSurfaceCreated(); + boolean isPreviewVideoSurfaceCreated(); + Surface getDisplayVideoSurface(); + Surface getPreviewVideoSurface(); + } +} -- cgit v1.2.3