From 350fff554bf7ed5b9e91985935488771156953ab Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Tue, 29 Jul 2014 11:52:22 -0700 Subject: Various changes. 1. Add InCallCameraManager to track active camera and manage camera capabilities. 2. Use new camera manager in CallButtonPresenter instead of directly accessing camera. 3. Implemented new camera setup flow between incall and telephony. 4. Landscape video call support (actively rotating while video is running crashes InCall at the moment, but you can start it in portrait or landscape fine). Includes ensuring layout works properly with RTL locales. 5. Added progress spinner to CallCardFragment, useful to show when an upgrade to video is pending. Bug: 16012946 Change-Id: Iff33422eec3a92d8cbeb217f5be2f1c9c5f3e98d --- InCallUI/AndroidManifest.xml | 70 --- InCallUI/res/layout-land/call_card_content.xml | 86 ++-- InCallUI/res/layout-land/video_call_views.xml | 35 ++ InCallUI/res/layout/call_card_content.xml | 93 ++-- InCallUI/res/layout/video_call_views.xml | 7 +- InCallUI/res/values/animation_constants.xml | 1 + InCallUI/res/values/dimens.xml | 6 + .../com/android/incallui/CallButtonFragment.java | 1 - .../com/android/incallui/CallButtonPresenter.java | 53 +-- .../src/com/android/incallui/CallCardFragment.java | 136 ++++++ .../com/android/incallui/CallCardPresenter.java | 27 +- InCallUI/src/com/android/incallui/CallList.java | 13 + .../android/incallui/CallVideoClientNotifier.java | 23 + .../src/com/android/incallui/InCallActivity.java | 23 + .../com/android/incallui/InCallCameraManager.java | 137 ++++++ .../src/com/android/incallui/InCallPresenter.java | 136 ++++++ .../android/incallui/InCallVideoCallListener.java | 2 + .../com/android/incallui/VideoCallFragment.java | 478 +++++++++++++++------ .../com/android/incallui/VideoCallPresenter.java | 286 +++++++++--- 19 files changed, 1238 insertions(+), 375 deletions(-) delete mode 100644 InCallUI/AndroidManifest.xml create mode 100644 InCallUI/res/layout-land/video_call_views.xml create mode 100644 InCallUI/src/com/android/incallui/InCallCameraManager.java (limited to 'InCallUI') diff --git a/InCallUI/AndroidManifest.xml b/InCallUI/AndroidManifest.xml deleted file mode 100644 index 62443dab9..000000000 --- a/InCallUI/AndroidManifest.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/InCallUI/res/layout-land/call_card_content.xml b/InCallUI/res/layout-land/call_card_content.xml index a03ab2dbb..b6aac56fd 100644 --- a/InCallUI/res/layout-land/call_card_content.xml +++ b/InCallUI/res/layout-land/call_card_content.xml @@ -16,16 +16,16 @@ ~ limitations under the License --> - + android:layout_height="match_parent" > - + - - - + android:gravity="start|center_vertical" + android:scaleType="centerCrop" + android:contentDescription="@string/contactPhoto" + android:src="@drawable/picture_unknown" /> + - - - + + + - + + + + diff --git a/InCallUI/res/layout-land/video_call_views.xml b/InCallUI/res/layout-land/video_call_views.xml new file mode 100644 index 000000000..7065d4515 --- /dev/null +++ b/InCallUI/res/layout-land/video_call_views.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/InCallUI/res/layout/call_card_content.xml b/InCallUI/res/layout/call_card_content.xml index 07b38860d..da21c30dc 100644 --- a/InCallUI/res/layout/call_card_content.xml +++ b/InCallUI/res/layout/call_card_content.xml @@ -16,10 +16,9 @@ ~ limitations under the License --> - + android:layout_height="match_parent" > - - + - - - + android:layout_height="match_parent" + android:layout_gravity="center_vertical" + android:gravity="top|center_horizontal" + android:scaleType="centerCrop" + android:contentDescription="@string/contactPhoto" + android:src="@drawable/picture_unknown" /> - + - - + + - + + - + + - + - + + diff --git a/InCallUI/res/layout/video_call_views.xml b/InCallUI/res/layout/video_call_views.xml index 08334b21e..ab03aa358 100644 --- a/InCallUI/res/layout/video_call_views.xml +++ b/InCallUI/res/layout/video_call_views.xml @@ -20,15 +20,16 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - - + \ No newline at end of file diff --git a/InCallUI/res/values/animation_constants.xml b/InCallUI/res/values/animation_constants.xml index f3b2c66d9..8df6a7281 100644 --- a/InCallUI/res/values/animation_constants.xml +++ b/InCallUI/res/values/animation_constants.xml @@ -18,4 +18,5 @@ 333 333 + 257 diff --git a/InCallUI/res/values/dimens.xml b/InCallUI/res/values/dimens.xml index ac82a1dbd..f99c5abaa 100644 --- a/InCallUI/res/values/dimens.xml +++ b/InCallUI/res/values/dimens.xml @@ -105,4 +105,10 @@ 70dp 50dp + + + 90dp + + 20dp diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index dd8446134..e07086879 100644 --- a/InCallUI/src/com/android/incallui/CallButtonFragment.java +++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java @@ -129,7 +129,6 @@ public class CallButtonFragment // set the buttons updateAudioButtons(getPresenter().getSupportedAudio()); - getPresenter().initializeCameraManager(getActivity().getApplicationContext()); } @Override diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index c0e8cd555..9a4a2770e 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -16,10 +16,6 @@ package com.android.incallui; -import android.content.Context; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraManager; import android.telecomm.CallCapabilities; import android.telecomm.InCallService.VideoCall; import android.telecomm.VideoCallProfile; @@ -45,18 +41,12 @@ public class CallButtonPresenter extends Presenter implements InCallStateListener, AudioModeListener, IncomingCallListener, - InCallDetailsListener { + InCallDetailsListener, InCallEventListener { private static final String TAG = CallCardPresenter.class.getSimpleName(); private static final long CALL_TIME_UPDATE_INTERVAL = 1000; // in milliseconds @@ -103,6 +104,7 @@ public class CallCardPresenter extends Presenter InCallPresenter.getInstance().addListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); InCallPresenter.getInstance().addDetailsListener(this); + InCallPresenter.getInstance().addInCallEventListener(this); } @Override @@ -113,6 +115,7 @@ public class CallCardPresenter extends Presenter InCallPresenter.getInstance().removeListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); InCallPresenter.getInstance().removeDetailsListener(this); + InCallPresenter.getInstance().removeInCallEventListener(this); AudioModeProvider.getInstance().removeListener(this); @@ -206,9 +209,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 + // Hide/show the contact photo based on the video state. + // If the primary call is a video call on hold, still show the contact photo. + // If the primary call is an active video call, hide the contact photo. if (mPrimary != null) { - getUi().setPhotoVisible(!mPrimary.isVideoCall()); + getUi().setPhotoVisible(!(mPrimary.isVideoCall() && callState != Call.State.ONHOLD)); } final boolean enableEndCallButton = Call.State.isConnected(callState) && @@ -597,8 +602,23 @@ public class CallCardPresenter extends Presenter return handle == null ? "" : handle.getSchemeSpecificPart(); } + /** + * Handles a change to the full screen video state. + * + * @param isFullScreenVideo {@code True} if the application is entering full screen video mode. + */ + @Override + public void onFullScreenVideoStateChanged(boolean isFullScreenVideo) { + final CallCardUi ui = getUi(); + if (ui == null) { + return; + } + ui.setCallCardVisible(!isFullScreenVideo); + } + public interface CallCardUi extends Ui { void setVisible(boolean on); + void setCallCardVisible(boolean visible); void setPrimary(String number, String name, boolean nameIsNumber, String label, Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall); void setSecondary(boolean show, String name, boolean nameIsNumber, String label, @@ -615,6 +635,7 @@ public class CallCardPresenter extends Presenter void setCallbackNumber(String number, boolean isEmergencyCalls); void setCallDetails(android.telecomm.Call.Details details); void setPhotoVisible(boolean isVisible); + void setProgressSpinnerVisible(boolean visible); } private TelecommManager getTelecommManager() { diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java index e4085f610..b59b0a6a9 100644 --- a/InCallUI/src/com/android/incallui/CallList.java +++ b/InCallUI/src/com/android/incallui/CallList.java @@ -444,6 +444,19 @@ public class CallList implements InCallPhoneListener { notifyGenericListeners(); } + /** + * Notifies all video calls of a change in device orientation. + * + * @param rotation The new rotation angle (in degrees). + */ + public void notifyCallsOfDeviceRotation(int rotation) { + for (Call call : mCallById.values()) { + if (call.getVideoCall() != null) { + call.getVideoCall().setDeviceOrientation(rotation); + } + } + } + /** * Handles the timeout for destroying disconnected calls. */ diff --git a/InCallUI/src/com/android/incallui/CallVideoClientNotifier.java b/InCallUI/src/com/android/incallui/CallVideoClientNotifier.java index d1e7f4c14..cc8b61daf 100644 --- a/InCallUI/src/com/android/incallui/CallVideoClientNotifier.java +++ b/InCallUI/src/com/android/incallui/CallVideoClientNotifier.java @@ -155,6 +155,19 @@ public class CallVideoClientNotifier { } } + /** + * Inform listeners of a change to camera dimensions. + * + * @param call The call. + * @param width The new camera video width. + * @param height The new camera video height. + */ + public void cameraDimensionsChanged(Call call, int width, int height) { + for (SurfaceChangeListener listener : mSurfaceChangeListeners) { + listener.onCameraDimensionsChange(call, width, height); + } + } + /** * Listener interface for any class that wants to be notified of upgrade to video and downgrade * to audio session modification requests. @@ -203,5 +216,15 @@ public class CallVideoClientNotifier { * @param height */ public void onUpdatePeerDimensions(Call call, int width, int height); + + /** + * Called when the local camera changes dimensions. This occurs when a change in camera + * occurs. + * + * @param call The call which experienced the camera dimension change. + * @param width The new camera video width. + * @param height The new camera video height. + */ + public void onCameraDimensionsChange(Call call, int width, int height); } } diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java index 001c76954..184a01023 100644 --- a/InCallUI/src/com/android/incallui/InCallActivity.java +++ b/InCallUI/src/com/android/incallui/InCallActivity.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.telephony.DisconnectCause; +import android.view.ViewTreeObserver; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.KeyEvent; @@ -77,6 +78,12 @@ public class InCallActivity extends Activity { } }; + /** + * Stores the current orientation of the activity. Used to determine if a change in orientation + * has occurred. + */ + private int mCurrentOrientation; + @Override protected void onCreate(Bundle icicle) { Log.d(this, "onCreate()... this = " + this); @@ -103,6 +110,7 @@ public class InCallActivity extends Activity { internalResolveIntent(getIntent()); + mCurrentOrientation = getResources().getConfiguration().orientation; mIsLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; mSlideIn = AnimationUtils.loadAnimation(this, @@ -361,12 +369,27 @@ public class InCallActivity extends Activity { @Override public void onConfigurationChanged(Configuration config) { InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config); + Log.d(this, "onConfigurationChanged "+config.orientation); + + // Check to see if the orientation changed to prevent triggering orientation change events + // for other configuration changes. + if (config.orientation != mCurrentOrientation) { + mCurrentOrientation = config.orientation; + InCallPresenter.getInstance().onDeviceRotationChange( + getWindowManager().getDefaultDisplay().getRotation()); + InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation); + } + super.onConfigurationChanged(config); } public CallButtonFragment getCallButtonFragment() { return mCallButtonFragment; } + public CallCardFragment getCallCardFragment() { + return mCallCardFragment; + } + private void internalResolveIntent(Intent intent) { final String action = intent.getAction(); diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java new file mode 100644 index 000000000..084a98f92 --- /dev/null +++ b/InCallUI/src/com/android/incallui/InCallCameraManager.java @@ -0,0 +1,137 @@ +/* + * 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.Context; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.util.Size; + +import java.lang.String; + +/** + * Used to track which camera is used for outgoing video. + */ +public class InCallCameraManager { + + /** + * The camera ID for the front facing camera. + */ + private String mFrontFacingCameraId; + + /** + * The camera ID for the rear facing camera. + */ + private String mRearFacingCameraId; + + /** + * The currently active camera. + */ + private boolean mUseFrontFacingCamera; + + /** + * Aspect ratio of the front facing camera. + */ + private float mFrontFacingCameraAspectRatio; + + /** + * Aspect ratio of the rear facing camera. + */ + private float mRearFacingCameraAspectRatio; + + /** + * Initializes the InCall CameraManager. + * + * @param context The current context. + */ + public InCallCameraManager(Context context) { + mUseFrontFacingCamera = true; + initializeCameraList(context); + } + + /** + * Sets whether the front facing camera should be used or not. + * + * @param useFrontFacingCamera {@code True} if the front facing camera is to be used. + */ + public void setUseFrontFacingCamera(boolean useFrontFacingCamera) { + mUseFrontFacingCamera = useFrontFacingCamera; + } + + /** + * Determines whether the front facing camera is currently in use. + * + * @return {@code True} if the front facing camera is in use. + */ + public boolean isUsingFrontFacingCamera() { + return mUseFrontFacingCamera; + } + + /** + * Determines the active camera ID. + * + * @return The active camera ID. + */ + public String getActiveCameraId() { + if (mUseFrontFacingCamera) { + return mFrontFacingCameraId; + } else { + return mRearFacingCameraId; + } + } + + /** + * Get the camera ID and aspect ratio for the front and rear cameras. + * + * @param context The context. + */ + private void initializeCameraList(Context context) { + CameraManager cameraManager = (CameraManager) context.getSystemService( + Context.CAMERA_SERVICE); + + String[] cameraIds = {}; + try { + cameraIds = cameraManager.getCameraIdList(); + } catch (CameraAccessException e) { + Log.d(this, "Could not access camera: "+e); + // Camera disabled by device policy. + return; + } + + for (int i = 0; i < cameraIds.length; i++) { + CameraCharacteristics c = null; + try { + c = cameraManager.getCameraCharacteristics(cameraIds[i]); + } catch (IllegalArgumentException e) { + // Device Id is unknown. + } catch (CameraAccessException e) { + // Camera disabled by device policy. + } + if (c != null) { + int facingCharacteristic = c.get(CameraCharacteristics.LENS_FACING); + if (facingCharacteristic == CameraCharacteristics.LENS_FACING_FRONT) { + mFrontFacingCameraId = cameraIds[i]; + } else if (facingCharacteristic == CameraCharacteristics.LENS_FACING_BACK) { + mRearFacingCameraId = cameraIds[i]; + } + } + } + } +} diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 3a8dc43b6..a53a23d81 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -19,16 +19,21 @@ package com.android.incallui; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.telecomm.CallCapabilities; import android.telecomm.Phone; import android.telecomm.PhoneAccountHandle; import android.telecomm.VideoCallProfile; +import android.text.TextUtils; +import android.view.Surface; +import android.view.View; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Locale; import java.util.Set; /** @@ -47,6 +52,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { private final Set mListeners = Sets.newHashSet(); private final ArrayList mIncomingCallListeners = Lists.newArrayList(); private final Set mDetailsListeners = Sets.newHashSet(); + private final Set mOrientationListeners = Sets.newHashSet(); + private final Set mInCallEventListeners = Sets.newHashSet(); private AudioModeProvider mAudioModeProvider; private StatusBarNotifier mStatusBarNotifier; @@ -58,6 +65,7 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { private ProximitySensor mProximitySensor; private boolean mServiceConnected = false; private boolean mAccountSelectionCancelled = false; + private InCallCameraManager mInCallCameraManager = null; private final Phone.Listener mPhoneListener = new Phone.Listener() { @Override @@ -401,6 +409,26 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { mDetailsListeners.remove(listener); } + public void addOrientationListener(InCallOrientationListener listener) { + Preconditions.checkNotNull(listener); + mOrientationListeners.add(listener); + } + + public void removeOrientationListener(InCallOrientationListener listener) { + Preconditions.checkNotNull(listener); + mOrientationListeners.remove(listener); + } + + public void addInCallEventListener(InCallEventListener listener) { + Preconditions.checkNotNull(listener); + mInCallEventListeners.add(listener); + } + + public void removeInCallEventListener(InCallEventListener listener) { + Preconditions.checkNotNull(listener); + mInCallEventListeners.remove(listener); + } + public ProximitySensor getProximitySensor() { return mProximitySensor; } @@ -635,6 +663,17 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } } + /** + * Called by the {@link VideoCallPresenter} to inform of a change in full screen video status. + * + * @param isFullScreenVideo {@code True} if entering full screen video mode. + */ + public void setFullScreenVideoState(boolean isFullScreenVideo) { + for (InCallEventListener listener : mInCallEventListeners) { + listener.onFullScreenVideoStateChanged(isFullScreenVideo); + } + } + /** * For some disconnected causes, we show a dialog. This calls into the activity to show * the dialog if appropriate for the call. @@ -828,6 +867,91 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { return intent; } + /** + * Retrieves the current in-call camera manager instance, creating if necessary. + * + * @return The {@link InCallCameraManager}. + */ + public InCallCameraManager getInCallCameraManager() { + synchronized(this) { + if (mInCallCameraManager == null) { + mInCallCameraManager = new InCallCameraManager(mContext); + } + + return mInCallCameraManager; + } + } + + /** + * Handles changes to the device rotation. + * + * @param rotation The device rotation. + */ + public void onDeviceRotationChange(int rotation) { + // First translate to rotation in degrees. + int rotationAngle; + switch (rotation) { + case Surface.ROTATION_0: + rotationAngle = 0; + break; + case Surface.ROTATION_90: + rotationAngle = 90; + break; + case Surface.ROTATION_180: + rotationAngle = 180; + break; + case Surface.ROTATION_270: + rotationAngle = 270; + break; + default: + rotationAngle = 0; + } + + mCallList.notifyCallsOfDeviceRotation(rotationAngle); + } + + /** + * Notifies listeners of changes in orientation (e.g. portrait/landscape). + * + * @param orientation The orientation of the device. + */ + public void onDeviceOrientationChange(int orientation) { + for (InCallOrientationListener listener : mOrientationListeners) { + listener.onDeviceOrientationChanged(orientation); + } + } + + /** + * Configures the in-call UI activity so it can change orientations or not. + * + * @param allowOrientationChange {@code True} if the in-call UI can change between portrait + * and landscape. {@Code False} if the in-call UI should be locked in portrait. + */ + public void setInCallAllowsOrientationChange(boolean allowOrientationChange) { + if (!allowOrientationChange) { + mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + } else { + mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + } + + /** + * Returns the space available beside the call card. + * + * @return The space beside the call card. + */ + public float getSpaceBesideCallCard() { + return mInCallActivity.getCallCardFragment().getSpaceBesideCallCard(); + } + + /** + * @return True if the application is currently running in a right-to-left locale. + */ + public static boolean isRtl() { + return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == + View.LAYOUT_DIRECTION_RTL; + } + /** * Private constructor. Must use getInstance() to get this singleton. */ @@ -879,4 +1003,16 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { public interface InCallDetailsListener { public void onDetailsChanged(Call call, android.telecomm.Call.Details details); } + + public interface InCallOrientationListener { + public void onDeviceOrientationChanged(int orientation); + } + + /** + * Interface implemented by classes that need to know about events which occur within the + * In-Call UI. Used as a means of communicating between fragments that make up the UI. + */ + public interface InCallEventListener { + public void onFullScreenVideoStateChanged(boolean isFullScreenVideo); + } } diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java index e1fe60ab3..c15b5907e 100644 --- a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java +++ b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java @@ -125,6 +125,8 @@ public class InCallVideoCallListener extends VideoCall.Listener { */ @Override public void onCameraCapabilitiesChanged(CallCameraCapabilities callCameraCapabilities) { + CallVideoClientNotifier.getInstance().cameraDimensionsChanged(mCall, + callCameraCapabilities.getWidth(), callCameraCapabilities.getHeight()); } /** diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java index 969ec01d2..8e0b3a398 100644 --- a/InCallUI/src/com/android/incallui/VideoCallFragment.java +++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java @@ -16,16 +16,18 @@ package com.android.incallui; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.SurfaceTexture; import android.os.Bundle; +import android.view.Display; import android.view.LayoutInflater; import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; +import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; - -import java.util.Set; +import android.view.ViewTreeObserver; /** * Fragment containing video calling surfaces. @@ -33,18 +35,21 @@ import java.util.Set; public class VideoCallFragment extends BaseFragment implements VideoCallPresenter.VideoCallUi { + /** + * Surface ID for the display surface. + */ 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}. + * Surface ID for the preview surface. */ - public interface VideoCallSurfaceListener { - void onSurfaceCreated(int surface); - void onSurfaceDestroyed(int surface); - void onSurfaceChanged(int surface, int format, int width, int height); - } + public static final int SURFACE_PREVIEW = 2; + + // Static storage used to retain the video surfaces across Activity restart. + // TextureViews are not parcelable, so it is not possible to store them in the saved state. + private static boolean sVideoSurfacesInUse = false; + private static VideoCallSurface sPreviewSurface = null; + private static VideoCallSurface sDisplaySurface = null; /** * {@link ViewStub} holding the video call surfaces. This is the parent for the @@ -59,42 +64,177 @@ public class VideoCallFragment extends BaseFragment + * When a call's video state changes to bi-directional video, the + * {@link com.android.incallui.VideoCallPresenter} performs the following negotiation with the + * telephony layer: + *
    + *
  • {@code VideoCallPresenter} creates and informs telephony of the display surface.
  • + *
  • {@code VideoCallPresenter} creates the preview surface.
  • + *
  • {@code VideoCallPresenter} informs telephony of the currently selected camera.
  • + *
  • Telephony layer sends {@link android.telecomm.CallCameraCapabilities}, including the + * dimensions of the video for the current camera.
  • + *
  • {@code VideoCallPresenter} adjusts size of the preview surface to match the aspect + * ratio of the camera.
  • + *
  • {@code VideoCallPresenter} informs telephony of the new preview surface.
  • + *
+ *

+ * When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both + * surfaces. */ public class VideoCallPresenter extends Presenter implements - IncomingCallListener, InCallStateListener, + IncomingCallListener, InCallOrientationListener, InCallStateListener, InCallDetailsListener, SurfaceChangeListener, VideoEventListener { + /** + * Determines the device orientation (portrait/lanscape). + */ + public int getDeviceOrientation() { + return mDeviceOrientation; + } + + /** + * Defines the state of the preview surface negotiation with the telephony layer. + */ + private class PreviewSurfaceState { + /** + * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet + * started. + */ + private static final int NONE = 0; + + /** + * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet + * been received. + */ + private static final int CAMERA_SET = 1; + + /** + * The camera capabilties have been received from telephony, but the surface has not yet + * been set on the {@link VideoCall}. + */ + private static final int CAPABILITIES_RECEIVED = 2; + + /** + * The surface has been set on the {@link VideoCall}. + */ + private static final int SURFACE_SET = 3; + } + + /** + * The minimum width or height of the preview surface. Used when re-sizing the preview surface + * to match the aspect ratio of the currently selected camera. + */ + private float mMinimumVideoDimension; + /** * The current context. */ @@ -47,7 +107,7 @@ public class VideoCallPresenter extends Presenter 0 && height > 0) { + aspectRatio = (float) width / (float) height; + } + setPreviewSize(mDeviceOrientation, aspectRatio); + + // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}. + // If it not yet ready, it will be set when when creation completes. + if (ui.isPreviewVideoSurfaceCreated()) { + mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; + mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + } + } + + /** + * Handles hanges to the device orientation. + * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} + * @param orientation The device orientation. + */ + @Override + public void onDeviceOrientationChanged(int orientation) { + mDeviceOrientation = orientation; + } + + /** + * Sets the preview surface size based on the current device orientation. + * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} + * + * @param orientation The device orientation. + * @param aspectRatio The aspect ratio of the camera (width / height). + */ + private void setPreviewSize(int orientation, float aspectRatio) { + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + int height; + int width; + + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + width = (int) (mMinimumVideoDimension * aspectRatio); + height = (int) mMinimumVideoDimension; + } else { + width = (int) mMinimumVideoDimension; + height = (int) (mMinimumVideoDimension * aspectRatio); + } + ui.setPreviewSize(width, height); + } + /** * Defines the VideoCallUI interactions. */ @@ -358,5 +535,8 @@ public class VideoCallPresenter extends Presenter