diff options
author | Tyler Gunn <tgunn@google.com> | 2014-07-30 00:10:53 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-07-27 22:35:46 +0000 |
commit | 37b5efe38f083d695c91857169e94d4f0ffc8c45 (patch) | |
tree | a92e777cbb12eedca23e3d37d68672fd183fde0c | |
parent | d093bd9afb3cc57da519668db98ba6684d42e6e6 (diff) | |
parent | 350fff554bf7ed5b9e91985935488771156953ab (diff) |
Merge "Various changes." into lmp-dev
19 files changed, 1238 insertions, 375 deletions
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 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.incallui" - coreApp="true" > - - <original-package android:name="com.android.incallui" /> - - <uses-permission android:name="android.permission.READ_CONTACTS" /> - <uses-permission android:name="android.permission.VIBRATE"/> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.WAKE_LOCK"/> - - <application - android:name="InCallApp" - android:label="@string/inCallLabel" - android:supportsRtl="true"> - - <!-- Main in-call UI activity. This is never launched directly - from outside the phone app; instead, it's either launched by - the OutgoingCallBroadcaster (for outgoing calls), or as the - fullScreenIntent of a notification (for incoming calls.) --> - <activity android:name=".InCallActivity" - android:theme="@style/Theme.InCallScreen" - android:label="@string/inCallLabel" - android:excludeFromRecents="true" - android:launchMode="singleInstance" - android:screenOrientation="nosensor" - android:configChanges="keyboardHidden" - android:exported="false"> - </activity> - - <service android:name="InCallServiceImpl"> - <intent-filter> - <action android:name="android.telecomm.InCallService" /> - </intent-filter> - </service> - - <service android:name="InCallVideoClient"> - <intent-filter> - <action android:name="android.telecomm.CallVideoClient" /> - </intent-filter> - </service> - - <!-- BroadcastReceiver for receiving Intents from Notification mechanism. --> - <receiver android:name="InCallApp$NotificationBroadcastReceiver" android:exported="false"> - <intent-filter> - <action android:name="com.android.incallui.ACTION_HANG_UP_ONGOING_CALL" /> - <action android:name="com.android.incallui.ACTION_ANSWER_VIDEO_INCOMING_CALL" /> - <action android:name="com.android.incallui.ACTION_ANSWER_VOICE_INCOMING_CALL" /> - <action android:name="com.android.incallui.ACTION_DECLINE_INCOMING_CALL" /> - </intent-filter> - </receiver> - - </application> -</manifest> 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 --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal" > + android:layout_height="match_parent" > <LinearLayout android:id="@+id/primary_call_info_container" - android:layout_width="0dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_weight="1" android:orientation="vertical" android:elevation="@dimen/primary_call_elevation" android:background="@color/incall_call_banner_background_color" @@ -51,35 +51,59 @@ </FrameLayout> </LinearLayout> - <FrameLayout - android:layout_width="0dp" + <!-- Contact photo for primary call info --> + <ImageView android:id="@+id/photo" + android:layout_toEndOf="@id/primary_call_info_container" + android:layout_width="match_parent" + android:layout_gravity="start|center_vertical" android:layout_height="match_parent" - android:layout_weight="1" > - - <!-- Contact photo for primary call info --> - <ImageView android:id="@+id/photo" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="top|center_horizontal" - android:scaleType="centerCrop" - android:contentDescription="@string/contactPhoto" - android:src="@drawable/picture_unknown" /> + android:gravity="start|center_vertical" + android:scaleType="centerCrop" + android:contentDescription="@string/contactPhoto" + android:src="@drawable/picture_unknown" /> + <fragment android:name="com.android.incallui.VideoCallFragment" + android:layout_alignParentStart="true" + android:layout_gravity="start|center_vertical" + android:id="@+id/videoCallFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> - <fragment android:name="com.android.incallui.DialpadFragment" - android:id="@+id/dialpadFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - - <fragment android:name="com.android.incallui.AnswerFragment" - android:id="@+id/answerFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="top" - android:layout_gravity="bottom|center_horizontal" - android:layout_marginBottom="@dimen/glowpadview_margin_bottom" - android:visibility="gone" /> + <!-- Progress spinner, useful for indicating pending operations such as upgrade to video. --> + <FrameLayout + android:id="@+id/progressSpinner" + android:layout_toEndOf="@id/primary_call_info_container" + android:background="#63000000" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:visibility="gone" > + <ProgressBar + android:id="@+id/progress_bar" + style="@android:style/Widget.Material.ProgressBar" + android:layout_gravity="center" + android:layout_width="48dp" + android:layout_height="48dp" + android:indeterminate="true" /> </FrameLayout> -</LinearLayout> + <fragment android:name="com.android.incallui.DialpadFragment" + android:id="@+id/dialpadFragment" + android:layout_toEndOf="@id/primary_call_info_container" + android:layout_gravity="end|center_vertical" + android:layout_alignParentEnd="true" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <fragment android:name="com.android.incallui.AnswerFragment" + android:id="@+id/answerFragment" + android:layout_toEndOf="@id/primary_call_info_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="start" + android:layout_gravity="end|center_vertical" + android:layout_marginBottom="@dimen/glowpadview_margin_bottom" + android:visibility="gone" /> +</RelativeLayout> 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <TextureView + android:id="@+id/incomingVideo" + android:layout_gravity="center_horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <!-- The width and height are replaced at runtime based on the selected camera. --> + <TextureView + android:id="@+id/previewVideo" + android:layout_gravity="bottom|right" + android:layout_margin="@dimen/video_preview_margin" + android:layout_width="70dp" + android:layout_height="120dp" /> +</FrameLayout>
\ 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 --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" > + android:layout_height="match_parent" > <LinearLayout android:id="@+id/primary_call_info_container" @@ -27,6 +26,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:elevation="@dimen/primary_call_elevation" + android:layout_centerHorizontal="true" android:background="@color/incall_call_banner_background_color" android:paddingTop="@dimen/call_banner_primary_call_container_top_padding" android:clipChildren="false" @@ -46,45 +46,66 @@ android:visibility="gone" android:padding="@dimen/call_banner_side_padding" android:background="@android:color/white" /> - </LinearLayout> - <FrameLayout + <!-- Contact photo for primary call info --> + <ImageView android:id="@+id/photo" + android:layout_below="@id/primary_call_info_container" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" > - - <!-- Contact photo for primary call info --> - <ImageView android:id="@+id/photo" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="top|center_horizontal" - android:scaleType="centerCrop" - android:contentDescription="@string/contactPhoto" - android:src="@drawable/picture_unknown" /> + 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" /> - <fragment android:name="com.android.incallui.VideoCallFragment" - android:id="@+id/videoCallFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" /> + <fragment android:name="com.android.incallui.VideoCallFragment" + android:id="@+id/videoCallFragment" + android:layout_alignParentTop="true" + android:layout_gravity="top|center_horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" /> - <!-- Secondary "Call info" block, for the background ("on hold") call. --> - <include layout="@layout/secondary_call_info" /> + <!-- Progress spinner, useful for indicating pending operations such as upgrade to video. --> + <FrameLayout + android:id="@+id/progressSpinner" + android:layout_below="@id/primary_call_info_container" + android:background="#63000000" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:visibility="gone" > - <fragment android:name="com.android.incallui.DialpadFragment" - android:id="@+id/dialpadFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" /> + <ProgressBar + android:id="@+id/progress_bar" + style="@android:style/Widget.Material.ProgressBar" + android:layout_gravity="center" + android:layout_width="48dp" + android:layout_height="48dp" + android:indeterminate="true" /> + </FrameLayout> - <fragment android:name="com.android.incallui.AnswerFragment" - android:id="@+id/answerFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="top" - android:layout_gravity="bottom|center_horizontal" - android:layout_marginBottom="@dimen/glowpadview_margin_bottom" - android:visibility="gone" /> + <!-- Secondary "Call info" block, for the background ("on hold") call. --> + <include layout="@layout/secondary_call_info" + android:layout_below="@id/primary_call_info_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - </FrameLayout> + <fragment android:name="com.android.incallui.DialpadFragment" + android:id="@+id/dialpadFragment" + android:layout_below="@id/primary_call_info_container" + android:layout_gravity="bottom|center_horizontal" + android:layout_alignParentBottom="true" + android:layout_width="match_parent" + android:layout_height="match_parent" /> -</LinearLayout> + <fragment android:name="com.android.incallui.AnswerFragment" + android:id="@+id/answerFragment" + android:layout_below="@id/primary_call_info_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="bottom|center_horizontal" + android:layout_marginBottom="@dimen/glowpadview_margin_bottom" + android:visibility="gone" /> +</RelativeLayout> 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" > - <SurfaceView + <TextureView android:id="@+id/incomingVideo" android:layout_gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> - <SurfaceView + <!-- The width and height are replaced at runtime based on the selected camera. --> + <TextureView android:id="@+id/previewVideo" android:layout_gravity="bottom|right" - android:layout_margin="10dp" + android:layout_margin="@dimen/video_preview_margin" android:layout_width="70dp" android:layout_height="120dp" /> </FrameLayout>
\ 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 @@ <!-- Duration of the InCallUI reveal and shrink animations after a call is placed --> <integer name="reveal_animation_duration">333</integer> <integer name="shrink_animation_duration">333</integer> + <integer name="video_animation_duration">257</integer> </resources> 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 @@ <dimen name="end_call_button_hide_offset">70dp</dimen> <dimen name="call_card_anim_translate_y_offset">50dp</dimen> + + <!-- The smaller dimension of the video preview. When in portrait orientation this is the + width of the preview. When in landscape, this is the height. --> + <dimen name="video_preview_small_dimension">90dp</dimen> + + <dimen name="video_preview_margin">20dp</dimen> </resources> 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 f91cc403b..a5b30732b 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<CallButtonPresenter.CallButto private boolean mPreviousMuteState = false; private boolean mShowGenericMerge = false; private boolean mShowManageConference = false; - private boolean mUseFrontFacingCamera = true; - private InCallState mPreviousState = null; - private CameraManager mCameraManager; + private InCallCameraManager mInCallCameraManager; public CallButtonPresenter() { } - public void initializeCameraManager(Context context) { - mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); - } - @Override public void onUiReady(CallButtonUi ui) { super.onUiReady(ui); @@ -66,6 +56,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto // register for call state changes last InCallPresenter.getInstance().addListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); + mInCallCameraManager = InCallPresenter.getInstance().getInCallCameraManager(); } @Override @@ -75,6 +66,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto InCallPresenter.getInstance().removeListener(this); AudioModeProvider.getInstance().removeListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); + mInCallCameraManager = null; } @Override @@ -250,14 +242,14 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto * false if we should switch to using the back-facing camera. */ public void switchCameraClicked(boolean useFrontFacingCamera) { - mUseFrontFacingCamera = useFrontFacingCamera; + mInCallCameraManager.setUseFrontFacingCamera(useFrontFacingCamera); VideoCall videoCall = mCall.getVideoCall(); if (videoCall == null) { return; } - String cameraId = getCameraId(); + String cameraId = mInCallCameraManager.getActiveCameraId(); if (cameraId != null) { videoCall.setCamera(cameraId); } @@ -281,7 +273,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto mCall.getVideoState() | VideoCallProfile.VideoState.PAUSED); videoCall.sendSessionModifyRequest(videoCallProfile); } else { - videoCall.setCamera(getCameraId()); + videoCall.setCamera(mInCallCameraManager.getActiveCameraId()); VideoCallProfile videoCallProfile = new VideoCallProfile( mCall.getVideoState() & ~VideoCallProfile.VideoState.PAUSED); videoCall.sendSessionModifyRequest(videoCallProfile); @@ -434,39 +426,6 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto } } - private String getCameraId() { - String[] cameraIds = {}; - String cameraId = null; - int targetCharacteristic = mUseFrontFacingCamera - ? CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK; - - try { - cameraIds = mCameraManager.getCameraIdList(); - } catch (CameraAccessException e) { - // Camera disabled by device policy. - } - - for (int i = 0; i < cameraIds.length; i++) { - CameraCharacteristics c = null; - try { - c = mCameraManager.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 == targetCharacteristic) { - cameraId = cameraIds[i]; - break; - } - } - } - - return cameraId; - } - public void refreshMuteState() { // Restore the previous mute state if (mAutomaticallyMuted && diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index 7b5c9b759..87e947c8c 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -34,6 +34,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityEvent; @@ -82,6 +83,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private View mSecondaryCallProviderInfo; private TextView mSecondaryCallProviderLabel; private ImageView mSecondaryCallProviderIcon; + private View mProgressSpinner; // Dark number info bar private TextView mInCallMessageLabel; @@ -95,6 +97,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr private float mTranslationOffset; private Animation mPulseAnimation; + private int mVideoAnimationDuration; @Override CallCardPresenter.CallCardUi getUi() { @@ -112,6 +115,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mRevealAnimationDuration = getResources().getInteger(R.integer.reveal_animation_duration); mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration); + mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration); mFloatingActionButtonHideOffset = getResources().getDimensionPixelOffset( R.dimen.end_call_button_hide_offset); mIsLandscape = getResources().getConfiguration().orientation @@ -163,6 +167,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr mPrimaryCallInfo = view.findViewById(R.id.primary_call_banner); mCallButtonsContainer = view.findViewById(R.id.callButtonFragment); mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage); + mProgressSpinner = view.findViewById(R.id.progressSpinner); mFloatingActionButtonContainer = view.findViewById( R.id.floating_end_call_action_button_container); @@ -218,6 +223,137 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr } } + /** + * Hides or shows the progress spinner. + * + * @param visible {@code True} if the progress spinner should be visible. + */ + @Override + public void setProgressSpinnerVisible(boolean visible) { + mProgressSpinner.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + /** + * Sets the visibility of the primary call card. + * Ensures that when the primary call card is hidden, the video surface slides over to fill the + * entire screen. + * + * @param visible {@code True} if the primary call card should be visible. + */ + @Override + public void setCallCardVisible(final boolean visible) { + // When animating the hide/show of the views in a landscape layout, we need to take into + // account whether we are in a left-to-right locale or a right-to-left locale and adjust + // the animations accordingly. + final boolean isLayoutRtl = InCallPresenter.isRtl(); + + // Retrieve here since at fragment creation time the incoming video view is not inflated. + final View videoView = getView().findViewById(R.id.incomingVideo); + + // Determine how much space there is below or to the side of the call card. + final float spaceBesideCallCard = getSpaceBesideCallCard(); + + // We need to translate the video surface, but we need to know its position after the layout + // has occurred so use a {@code ViewTreeObserver}. + final ViewTreeObserver observer = getView().getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // We don't want to continue getting called. + if (observer.isAlive()) { + observer.removeOnPreDrawListener(this); + } + + float videoViewTranslation = 0f; + + // Translate the call card to its pre-animation state. + if (mIsLandscape) { + float translationX = mPrimaryCallCardContainer.getWidth(); + translationX *= isLayoutRtl ? 1 : -1; + + mPrimaryCallCardContainer.setTranslationX(visible ? translationX : 0); + + if (visible) { + videoViewTranslation = videoView.getWidth() / 2 - spaceBesideCallCard / 2; + videoViewTranslation *= isLayoutRtl ? -1 : 1; + } + } else { + mPrimaryCallCardContainer.setTranslationY(visible ? + -mPrimaryCallCardContainer.getHeight() : 0); + + if (visible) { + videoViewTranslation = videoView.getHeight() / 2 - spaceBesideCallCard / 2; + } + } + + // Perform animation of video view. + ViewPropertyAnimator videoViewAnimator = videoView.animate() + .setInterpolator(AnimUtils.EASE_OUT_EASE_IN) + .setDuration(mVideoAnimationDuration); + if (mIsLandscape) { + videoViewAnimator + .translationX(videoViewTranslation) + .start(); + } else { + videoViewAnimator + .translationY(videoViewTranslation) + .start(); + } + videoViewAnimator.start(); + + // Animate the call card sliding. + ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate() + .setInterpolator(AnimUtils.EASE_OUT_EASE_IN) + .setDuration(mVideoAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (!visible) { + mPrimaryCallCardContainer.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + if (visible) { + mPrimaryCallCardContainer.setVisibility(View.VISIBLE); + } + } + }); + + if (mIsLandscape) { + float translationX = mPrimaryCallCardContainer.getWidth(); + translationX *= isLayoutRtl ? 1 : -1; + callCardAnimator + .translationX(visible ? 0 : translationX) + .start(); + } else { + callCardAnimator + .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight()) + .start(); + } + + return true; + } + }); + } + + /** + * Determines the amount of space below the call card for portrait layouts), or beside the + * call card for landscape layouts. + * + * @return The amount of space below or beside the call card. + */ + public float getSpaceBesideCallCard() { + if (mIsLandscape) { + return getView().getWidth() - mPrimaryCallCardContainer.getWidth(); + } else { + return getView().getHeight() - mPrimaryCallCardContainer.getHeight(); + } + } + @Override public void setPrimaryName(String name, boolean nameIsNumber) { if (TextUtils.isEmpty(name)) { diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index 933b61a7f..7018db9d1 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -37,6 +37,7 @@ import com.android.incallui.AudioModeProvider.AudioModeListener; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; import com.android.incallui.InCallPresenter.InCallDetailsListener; +import com.android.incallui.InCallPresenter.InCallEventListener; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; @@ -49,7 +50,7 @@ import com.google.common.base.Preconditions; */ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> 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<CallCardPresenter.CallCardUi> 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<CallCardPresenter.CallCardUi> 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<CallCardPresenter.CallCardUi> 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<CallCardPresenter.CallCardUi> 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<CallCardPresenter.CallCardUi> 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 @@ -445,6 +445,19 @@ public class CallList implements InCallPhoneListener { } /** + * 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. */ private Handler mHandler = new Handler() { 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 @@ -156,6 +156,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 14216bea7..05a3f03af 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<InCallStateListener> mListeners = Sets.newHashSet(); private final ArrayList<IncomingCallListener> mIncomingCallListeners = Lists.newArrayList(); private final Set<InCallDetailsListener> mDetailsListeners = Sets.newHashSet(); + private final Set<InCallOrientationListener> mOrientationListeners = Sets.newHashSet(); + private final Set<InCallEventListener> 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; } @@ -636,6 +664,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. */ @@ -829,6 +868,91 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener { } /** + * 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. */ private InCallPresenter() { @@ -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 ef9d08a8e..2e0f12b4a 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<VideoCallPresenter, VideoCallPresenter.VideoCallUi> 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<VideoCallPresenter, private View mVideoViews; /** - * The display video {@link SurfaceView}. Incoming video from the remote party of the video - * call is displayed here. + * {@code True} when the entering the activity again after a restart due to orientation change. */ - private SurfaceView mDisplayVideoSurface; + private boolean mIsActivityRestart; /** - * 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. + * {@code True} when the layout of the activity has been completed. */ - private SurfaceHolder mDisplayVideoSurfaceHolder; + private boolean mIsLayoutComplete = false; /** - * Determines if the display surface has been created or not. + * {@code True} if in landscape mode. */ - private boolean mDisplayVideoSurfaceCreated; + private boolean mIsLandscape; /** - * The preview video {@link SurfaceView}. A preview of the outgoing video to the remote party - * of the video call is displayed here. + * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and + * {@link Surface}. Used to manage the lifecycle of these objects across device orientation + * changes. */ - private SurfaceView mPreviewVideoSurface; + private class VideoCallSurface implements TextureView.SurfaceTextureListener, + View.OnClickListener { + private int mSurfaceId; + private TextureView mTextureView; + private SurfaceTexture mSavedSurfaceTexture; + private Surface mSavedSurface; - /** - * 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; + /** + * Creates an instance of a {@link VideoCallSurface}. + * + * @param surfaceId The surface ID of the surface. + * @param textureView The {@link TextureView} for the surface. + */ + public VideoCallSurface(int surfaceId, TextureView textureView) { + mSurfaceId = surfaceId; + recreateView(textureView); + } - /** - * Determines if the preview surface has been created or not. - */ - private boolean mPreviewVideoSurfaceCreated; + /** + * Recreates a {@link VideoCallSurface} after a device orientation change. Re-applies the + * saved {@link SurfaceTexture} to the + * + * @param view The {@link TextureView}. + */ + public void recreateView(TextureView view) { + mTextureView = view; + mTextureView.setSurfaceTextureListener(this); + mTextureView.setOnClickListener(this); + + if (mSavedSurfaceTexture != null) { + mTextureView.setSurfaceTexture(mSavedSurfaceTexture); + } + } + + /** + * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has + * been successfully created. + * + * @param surfaceTexture The {@link SurfaceTexture} which has been created. + * @param width The width of the {@link SurfaceTexture}. + * @param height The height of the {@link SurfaceTexture}. + */ + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, + int height) { + // Where there is no saved {@link SurfaceTexture} available, use the newly created one. + // If a saved {@link SurfaceTexture} is available, we are re-creating after an + // orientation change. + if (mSavedSurfaceTexture == null) { + mSavedSurfaceTexture = surfaceTexture; + mSavedSurface = new Surface(mSavedSurfaceTexture); + } + + // Inform presenter that the surface is available. + getPresenter().onSurfaceCreated(mSurfaceId); + } + + /** + * Handles a change in the {@link SurfaceTexture}'s size. + * + * @param surfaceTexture The {@link SurfaceTexture}. + * @param width The new width. + * @param height The new height. + */ + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, + int height) { + // Not handled + } + + /** + * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed. + * + * @param surfaceTexture The {@link SurfaceTexture}. + * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}. + */ + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + /** + * Destroying the surface texture; inform the presenter so it can null the surfaces. + */ + if (mSavedSurfaceTexture == null) { + getPresenter().onSurfaceDestroyed(mSurfaceId); + if (mSavedSurface != null) { + mSavedSurface.release(); + mSavedSurface = null; + } + } + + // The saved SurfaceTexture will be null if we're shutting down, so we want to + // return "true" in that case (indicating that TextureView can release the ST). + return (mSavedSurfaceTexture == null); + } + + /** + * Handles {@link SurfaceTexture} update callback. + * @param surface + */ + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + // Not Handled + } + + /** + * Retrieves the current {@link TextureView}. + * + * @return The {@link TextureView}. + */ + public TextureView getTextureView() { + return mTextureView; + } + + /** + * Called by the user presenter to indicate that the surface is no longer required due to a + * change in video state. Releases and clears out the saved surface and surface textures. + */ + public void setDoneWithSurface() { + if (mSavedSurface != null) { + mSavedSurface.release(); + mSavedSurface = null; + } + if (mSavedSurfaceTexture != null) { + mSavedSurfaceTexture.release(); + mSavedSurfaceTexture = null; + } + } + + /** + * Retrieves the saved surface instance. + * + * @return The surface. + */ + public Surface getSurface() { + return mSavedSurface; + } + + /** + * Handles a user clicking the surface, which is the trigger to toggle the full screen + * Video UI. + * + * @param view The view receiving the click. + */ + @Override + public void onClick(View view) { + getPresenter().onSurfaceClick(mSurfaceId); + } + }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mIsActivityRestart = sVideoSurfacesInUse; } /** @@ -106,6 +246,9 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mIsLandscape = getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + getPresenter().init(getActivity()); } @@ -120,9 +263,66 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, @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); + final View view = inflater.inflate(R.layout.video_call_fragment, container, false); + + // Attempt to center the incoming video view, if it is in the layout. + final ViewTreeObserver observer = view.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Check if the layout includes the incoming video surface -- this will only be the + // case for a video call. + View displayVideo = view.findViewById(R.id.incomingVideo); + if (displayVideo != null) { + centerDisplayView(displayVideo); + } + + mIsLayoutComplete = true; + + // Remove the listener so we don't continually re-layout. + ViewTreeObserver observer = view.getViewTreeObserver(); + if (observer.isAlive()) { + observer.removeOnGlobalLayoutListener(this); + } + } + }); + + return view; + } + + /** + * Centers the display view vertically for portrait orientation, and horizontally for + * lanscape orientations. The view is centered within the available space not occupied by + * the call card. + * + * @param displayVideo The video view to center. + */ + private void centerDisplayView(View displayVideo) { + // In a lansdcape layout we need to ensure we horizontally center the view based on whether + // the layout is left-to-right or right-to-left. + // In a left-to-right locale, the space for the video view is to the right of the call card + // so we need to translate it in the +X direction. + // In a right-to-left locale, the space for the video view is to the left of the call card + // so we need to translate it in the -X direction. + final boolean isLayoutRtl = InCallPresenter.isRtl(); + + float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard(); + if (mIsLandscape) { + float videoViewTranslation = displayVideo.getWidth() / 2 + - spaceBesideCallCard / 2; + if (isLayoutRtl) { + displayVideo.setTranslationX(-videoViewTranslation); + } else { + displayVideo.setTranslationX(videoViewTranslation); + } + } else { + float videoViewTranslation = displayVideo.getHeight() / 2 + - spaceBesideCallCard / 2; + displayVideo.setTranslationY(videoViewTranslation); + } } /** @@ -136,6 +336,13 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, super.onViewCreated(view, savedInstanceState); mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub); + + // If the surfaces are already in use, we have just changed orientation or otherwise + // re-created the fragment. In this case we need to inflate the video call views and + // restore the surfaces. + if (sVideoSurfacesInUse) { + inflateVideoCallViews(); + } } /** @@ -156,104 +363,55 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, } /** - * 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) { - 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) { - 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()) { - surface = SURFACE_DISPLAY; - } else { - 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); + int visibility = show ? View.VISIBLE : View.GONE; + getView().setVisibility(visibility); if (show) { inflateVideoCallViews(); + } else { + cleanupSurfaces(); } if (mVideoViews != null ) { - int newVisibility = show ? View.VISIBLE : View.GONE; - mVideoViews.setVisibility(newVisibility); - mDisplayVideoSurface.setVisibility(newVisibility); - mPreviewVideoSurface.setVisibility(newVisibility); - mPreviewVideoSurface.setZOrderOnTop(show); + mVideoViews.setVisibility(visibility); } } /** + * Cleans up the video telephony surfaces. Used when the presenter indicates a change to an + * audio-only state. Since the surfaces are static, it is important to ensure they are cleaned + * up promptly. + */ + @Override + public void cleanupSurfaces() { + if (sDisplaySurface != null) { + sDisplaySurface.setDoneWithSurface(); + sDisplaySurface = null; + } + if (sPreviewSurface != null) { + sPreviewSurface.setDoneWithSurface(); + sPreviewSurface = null; + } + sVideoSurfacesInUse = false; + } + + @Override + public boolean isActivityRestart() { + return mIsActivityRestart; + } + + /** * @return {@code True} if the display video surface has been created. */ @Override public boolean isDisplayVideoSurfaceCreated() { - return mDisplayVideoSurfaceCreated; + return sDisplaySurface != null && sDisplaySurface.getSurface() != null; } /** @@ -261,49 +419,107 @@ public class VideoCallFragment extends BaseFragment<VideoCallPresenter, */ @Override public boolean isPreviewVideoSurfaceCreated() { - return mPreviewVideoSurfaceCreated; + return sPreviewSurface != null && sPreviewSurface.getSurface() != null; } /** * {@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. */ + @Override public Surface getDisplayVideoSurface() { - if (mDisplayVideoSurfaceHolder != null) { - return mDisplayVideoSurfaceHolder.getSurface(); - } - return null; + return sDisplaySurface == null ? null : sDisplaySurface.getSurface(); } /** * {@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. */ + @Override public Surface getPreviewVideoSurface() { - if (mPreviewVideoSurfaceHolder != null) { - return mPreviewVideoSurfaceHolder.getSurface(); + return sPreviewSurface == null ? null : sPreviewSurface.getSurface(); + } + + /** + * Changes the dimensions of the preview surface. Called when the dimensions change due to a + * device orientation change. + * + * @param width The new width. + * @param height The new height. + */ + @Override + public void setPreviewSize(int width, int height) { + if (sPreviewSurface != null) { + TextureView preview = sPreviewSurface.getTextureView(); + + if (preview == null ) { + return; + } + + ViewGroup.LayoutParams params = preview.getLayoutParams(); + params.width = width; + params.height = height; + preview.setLayoutParams(params); } - 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. + * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary, + * and creates {@link VideoCallSurface} instances to track the surfaces. */ private void inflateVideoCallViews() { - if (mDisplayVideoSurface == null && mPreviewVideoSurface == null && mVideoViews == null ) { + if (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); + if (mVideoViews != null) { + TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo); + setSurfaceSizeAndTranslation(displaySurface); + + if (!sVideoSurfacesInUse) { + // Where the video surfaces are not already in use (first time creating them), + // setup new VideoCallSurface instances to track them. + sDisplaySurface = new VideoCallSurface(SURFACE_DISPLAY, + (TextureView) mVideoViews.findViewById(R.id.incomingVideo)); + sPreviewSurface = new VideoCallSurface(SURFACE_PREVIEW, + (TextureView) mVideoViews.findViewById(R.id.previewVideo)); + sVideoSurfacesInUse = true; + } else { + // In this case, the video surfaces are already in use (we are recreating the + // Fragment after a destroy/create cycle resulting from a rotation. + sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById( + R.id.incomingVideo)); + sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById( + R.id.previewVideo)); } } } + + /** + * Resizes a surface so that it has the same size as the full screen and so that it is + * centered vertically below the call card. + * + * @param textureView The {@link TextureView} to resize and position. + */ + private void setSurfaceSizeAndTranslation(TextureView textureView) { + // Get current screen size. + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + + // Set the surface to have that size. + ViewGroup.LayoutParams params = textureView.getLayoutParams(); + params.width = size.x; + params.height = size.y; + textureView.setLayoutParams(params); + + // It is only possible to center the display view if layout of the views has completed. + // It is only after layout is complete that the dimensions of the Call Card has been + // established, which is a prerequisite to centering the view. + // Incoming video calls will center the view + if (mIsLayoutComplete && ((mIsLandscape && textureView.getTranslationX() == 0) || ( + !mIsLandscape && textureView.getTranslationY() == 0))) { + centerDisplayView(textureView); + } + } } diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java index 01049d731..87d09b118 100644 --- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java +++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java @@ -21,10 +21,12 @@ import com.google.common.base.Preconditions; import com.android.incallui.CallVideoClientNotifier.SurfaceChangeListener; import com.android.incallui.CallVideoClientNotifier.VideoEventListener; import com.android.incallui.InCallPresenter.InCallDetailsListener; +import com.android.incallui.InCallPresenter.InCallOrientationListener; import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import android.content.Context; +import android.content.res.Configuration; import android.telecomm.InCallService.VideoCall; import android.view.Surface; @@ -34,12 +36,70 @@ 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}. + * <p> + * 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: + * <ul> + * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.</li> + * <li>{@code VideoCallPresenter} creates the preview surface.</li> + * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.</li> + * <li>Telephony layer sends {@link android.telecomm.CallCameraCapabilities}, including the + * dimensions of the video for the current camera.</li> + * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect + * ratio of the camera.</li> + * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.</li> + * </ul> + * <p> + * When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both + * surfaces. */ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> 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. */ private Context mContext; @@ -47,7 +107,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi /** * The call the video surfaces are currently related to */ - private Call mCall; + private Call mPrimaryCall; /** * The {@link VideoCall} used to inform the video telephony layer of changes to the video @@ -61,12 +121,34 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi private boolean mIsVideoCall; /** + * Determines the active camera for transmitted video. + */ + private InCallCameraManager mInCallCameraManager; + + /** + * Determines the device orientation (portrait/lanscape). + */ + private int mDeviceOrientation; + + /** + * Tracks the state of the preview surface negotiation with the telephony layer. + */ + private int mPreviewSurfaceState = PreviewSurfaceState.NONE; + + /** + * Determines whether the video surface is in full-screen mode. + */ + private boolean mIsFullScreen = false; + + /** * Initializes the presenter. * * @param context The current context. */ public void init(Context context) { mContext = Preconditions.checkNotNull(context); + mMinimumVideoDimension = mContext.getResources().getDimension( + R.dimen.video_preview_small_dimension); } /** @@ -81,11 +163,13 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi // Register for call state changes last InCallPresenter.getInstance().addListener(this); InCallPresenter.getInstance().addIncomingCallListener(this); + InCallPresenter.getInstance().addOrientationListener(this); // Register for surface and video events from {@link InCallVideoProvider}s. CallVideoClientNotifier.getInstance().addSurfaceChangeListener(this); CallVideoClientNotifier.getInstance().addVideoEventListener(this); + mInCallCameraManager = InCallPresenter.getInstance().getInCallCameraManager(); mIsVideoCall = false; } @@ -94,13 +178,17 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * * @param ui The Ui implementation that is no longer ready to be used. */ - public void unUiUnready(VideoCallUi ui) { + @Override + public void onUiUnready(VideoCallUi ui) { super.onUiUnready(ui); InCallPresenter.getInstance().removeListener(this); InCallPresenter.getInstance().removeIncomingCallListener(this); + InCallPresenter.getInstance().removeOrientationListener(this); CallVideoClientNotifier.getInstance().removeSurfaceChangeListener(this); CallVideoClientNotifier.getInstance().removeVideoEventListener(this); + + mInCallCameraManager = null; } /** @@ -122,10 +210,15 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi return; } - if (surface == VideoCallFragment.SURFACE_DISPLAY) { - mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); - } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { + // If the preview surface has just been created and we have already received camera + // capabilities, but not yet set the surface, we will set the surface now. + if (surface == VideoCallFragment.SURFACE_PREVIEW && + mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { + + mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + } else if (surface == VideoCallFragment.SURFACE_DISPLAY) { + mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); } } @@ -148,7 +241,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ public void onSurfaceDestroyed(int surface) { final VideoCallUi ui = getUi(); - if (ui == null || mVideoCall == null) { return; } @@ -161,6 +253,19 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** + * Handles clicks on the video surfaces by toggling full screen state. + * Informs the {@link InCallPresenter} of the change so that it can inform the + * {@link CallCardPresenter} of the change. + * + * @param surfaceId The video surface receiving the click. + */ + public void onSurfaceClick(int surfaceId) { + mIsFullScreen = !mIsFullScreen; + InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); + } + + + /** * Handles incoming calls. * * @param state The in call state. @@ -180,28 +285,37 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @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 (state == InCallPresenter.InCallState.NO_CALLS) { + exitVideoState(); } - if (call == null || getUi() == null) { - return; + // Determine the primary active call). + Call primary = null; + if (state == InCallPresenter.InCallState.INCOMING) { + primary = callList.getIncomingCall(); + } else if (state == InCallPresenter.InCallState.OUTGOING) { + primary = callList.getOutgoingCall(); + } else if (state == InCallPresenter.InCallState.INCALL) { + primary = callList.getActiveCall(); } - Log.d(this, "onStateChange "+call); - - final boolean callChanged = !Objects.equals(mCall, call); - - // If the call changed track it now. - if (callChanged) { - mCall = call; + final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); + if (primaryChanged) { + mPrimaryCall = primary; + + if (primary != null) { + checkForCallVideoProviderChange(); + mIsVideoCall = mPrimaryCall.isVideoCall(); + if (mIsVideoCall) { + enterVideoState(); + } else { + exitVideoState(); + } + } else if (primary == null) { + // If no primary call, ensure we exit video state and clean up the video surfaces. + exitVideoState(); + } } - - checkForCallVideoProviderChange(); - checkForVideoStateChange(); } /** @@ -213,10 +327,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onDetailsChanged(Call call, android.telecomm.Call.Details details) { - Log.d(this, "onDetailsChanged "+call); - // If the details change is not for the currently active call no update is required. - if (!call.equals(mCall)) { + if (!call.equals(mPrimaryCall)) { return; } @@ -227,7 +339,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * Checks for a change to the call video provider and changes it if required. */ private void checkForCallVideoProviderChange() { - VideoCall videoCall = mCall.getTelecommCall().getVideoCall(); + VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall(); if (!Objects.equals(videoCall, mVideoCall)) { changeVideoCall(videoCall); } @@ -237,7 +349,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * Checks to see if the current video state has changed and updates the UI if required. */ private void checkForVideoStateChange() { - boolean newVideoState = mCall.isVideoCall(); + boolean newVideoState = mPrimaryCall.isVideoCall(); // Check if video state changed if (mIsVideoCall != newVideoState) { @@ -258,8 +370,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * @param videoCall The new video call. */ private void changeVideoCall(VideoCall videoCall) { - Log.d(this, "changeCallVideoProvider"); - // Null out the surfaces on the previous provider if (mVideoCall != null) { mVideoCall.setDisplaySurface(null); @@ -267,8 +377,6 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } mVideoCall = videoCall; - setSurfaces(); - } /** @@ -276,31 +384,29 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * TODO(vt): Need to adjust size and orientation of preview surface here. */ private void enterVideoState() { - Log.d(this, "enterVideoState"); VideoCallUi ui = getUi(); if (ui == null) { return; } ui.showVideoUi(true); - } + InCallPresenter.getInstance().setInCallAllowsOrientationChange(true); - /** - * Sets the surfaces on the specified {@link Call.VideoCall}. - */ - private void setSurfaces() { - Log.d(this, "setSurfaces"); - VideoCallUi ui = getUi(); - if (ui == null || mVideoCall == null) { - return; - } + // Communicate the current camera to telephony; expect to get call-back with camera + // capabilities. + if (mVideoCall != null) { + // Do not reset the surfaces if we just restarted the activity due to an orientation + // change. + if (ui.isActivityRestart()) { + return; + } - if (getUi().isDisplayVideoSurfaceCreated()) { - mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); - } + mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; + mVideoCall.setCamera(mInCallCameraManager.getActiveCameraId()); - if (getUi().isPreviewVideoSurfaceCreated()) { - mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); + if (ui.isDisplayVideoSurfaceCreated()) { + mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); + } } } @@ -308,12 +414,11 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi * Exits video mode by hiding the video surfaces. */ private void exitVideoState() { - Log.d(this, "exitVideoState"); VideoCallUi ui = getUi(); if (ui == null) { return; } - + InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); ui.showVideoUi(false); } @@ -326,7 +431,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onPeerPauseStateChanged(Call call, boolean paused) { - if (!call.equals(mCall)) { + if (!call.equals(mPrimaryCall)) { return; } @@ -342,7 +447,7 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi */ @Override public void onUpdatePeerDimensions(Call call, int width, int height) { - if (!call.equals(mCall)) { + if (!call.equals(mPrimaryCall)) { return; } @@ -350,6 +455,78 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi } /** + * Handles a change to the dimensions of the local camera. Receiving the camera capabilities + * triggers the creation of the video + * + * @param call The call which experienced the camera dimension change. + * @param width The new camera video width. + * @param height The new camera video height. + */ + @Override + public void onCameraDimensionsChange(Call call, int width, int height) { + VideoCallUi ui = getUi(); + if (ui == null) { + return; + } + + if (!call.equals(mPrimaryCall)) { + return; + } + + mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; + + // Configure the preview surface to the correct aspect ratio. + float aspectRatio = 1.0f; + if (width > 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. */ public interface VideoCallUi extends Ui { @@ -358,5 +535,8 @@ public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi boolean isPreviewVideoSurfaceCreated(); Surface getDisplayVideoSurface(); Surface getPreviewVideoSurface(); + void setPreviewSize(int width, int height); + void cleanupSurfaces(); + boolean isActivityRestart(); } } |