summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Gunn <tgunn@google.com>2014-07-29 11:52:22 -0700
committerTyler Gunn <tgunn@google.com>2014-07-29 11:52:22 -0700
commit350fff554bf7ed5b9e91985935488771156953ab (patch)
treead0e9b2b64d3db1ec7d9d66ba756a33de0642ed1
parent3a1698fd4c0957ad882c5994e703ad631e24c3bd (diff)
Various changes.
1. Add InCallCameraManager to track active camera and manage camera capabilities. 2. Use new camera manager in CallButtonPresenter instead of directly accessing camera. 3. Implemented new camera setup flow between incall and telephony. 4. Landscape video call support (actively rotating while video is running crashes InCall at the moment, but you can start it in portrait or landscape fine). Includes ensuring layout works properly with RTL locales. 5. Added progress spinner to CallCardFragment, useful to show when an upgrade to video is pending. Bug: 16012946 Change-Id: Iff33422eec3a92d8cbeb217f5be2f1c9c5f3e98d
-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();
}
}