summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--InCallUI/AndroidManifest.xml70
-rw-r--r--InCallUI/res/layout-land/call_card_content.xml86
-rw-r--r--InCallUI/res/layout-land/video_call_views.xml35
-rw-r--r--InCallUI/res/layout/call_card_content.xml93
-rw-r--r--InCallUI/res/layout/video_call_views.xml7
-rw-r--r--InCallUI/res/values/animation_constants.xml1
-rw-r--r--InCallUI/res/values/dimens.xml6
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonFragment.java1
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java53
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java136
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java27
-rw-r--r--InCallUI/src/com/android/incallui/CallList.java13
-rw-r--r--InCallUI/src/com/android/incallui/CallVideoClientNotifier.java23
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java23
-rw-r--r--InCallUI/src/com/android/incallui/InCallCameraManager.java137
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java136
-rw-r--r--InCallUI/src/com/android/incallui/InCallVideoCallListener.java2
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallFragment.java478
-rw-r--r--InCallUI/src/com/android/incallui/VideoCallPresenter.java286
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 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<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.VIDEO_STATE_PAUSED);
videoCall.sendSessionModifyRequest(videoCallProfile);
} else {
- videoCall.setCamera(getCameraId());
+ videoCall.setCamera(mInCallCameraManager.getActiveCameraId());
VideoCallProfile videoCallProfile = new VideoCallProfile(
mCall.getVideoState() & ~VideoCallProfile.VIDEO_STATE_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 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<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 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<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();
}
}